You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

553 lines
29 KiB

/* Один handle на поток файла. Router управляет отправкой и подтверждениями;
* ограниченное кольцо связывает uasync с общим FIFO дискового worker. */
#include "private.h"
#include "disk.h"
#include "../utun_instance.h"
#include "../chat/chat_event.h"
#include "../routing_layer/conn_mgr.h"
#include "../routing_layer/etcp_router.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <string.h>
#include <stdio.h>
#define TRANSFER_SLOTS 32
#define TRANSFER_BATCH 16
#define TRANSFER_MAX 16
#define TRANSFER_TIMEOUT_TB 1200000ULL
enum transfer_work { TRANSFER_PREPARE, TRANSFER_READ, TRANSFER_WRITE, TRANSFER_VERIFY, TRANSFER_FINISH };
struct mf_chunk { uint32_t bytes; uint8_t data[MF_BLOCK_SIZE]; };
struct mf_transfer {
struct mf_transfer* next;
struct member_files* files;
struct mf_transfer_request request;
struct mf_pending* pending;
struct CONN_MGR_HANDLE* cm;
struct mf_work* cleanup;
struct queue_waiter_handle waiter;
void* pump;
mf_transfer_fn callback;
void* arg;
struct mf_message open, result, outgoing;
struct mf_disk_info source;
EVP_MD_CTX* digest;
int fd, server, sending, starting, active, opened, busy, stopping, finished_io, peer_down;
int status, work_status, reply_ready, reply_final, verified, committed, end_sent, end_received;
enum transfer_work work;
uint64_t size, offset, received, read_offset, io_offset, io_bytes, io_size, progress_tb, last_event, last_log, logged_offset;
unsigned head, count, io_head, io_count;
uint8_t upload[16], version[16], hash[32];
char temp[MF_PATH_MAX + 80];
struct mf_chunk chunks[TRANSFER_SLOTS];
};
static void transfer_pump(void* arg);
static void transfer_submit(struct mf_transfer* t, enum transfer_work work);
static void transfer_stop(struct mf_transfer* t, int status);
static void transfer_send_open(struct mf_transfer* t, uint8_t command);
static void transfer_event(struct mf_transfer* t, int finished) {
if (t->server || !t->callback) return;
uint64_t now = get_time_tb();
if (!finished && t->last_event && now - t->last_event < 1000) return;
t->last_event = now;
struct mf_transfer_progress progress = { t->request.id, t->offset, t->size, (uint16_t)t->status, (uint8_t)finished };
t->callback(t->arg, &progress);
}
static void transfer_wake(struct mf_transfer* t) {
if (t->pump || t->waiter.internal || t->waiter.call_soon_id || t->files->closing) return;
t->pump = uasync_call_soon(t->files->inst->ua, t, transfer_pump);
if (!t->pump) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: stream pump allocation failed id=%llu", (unsigned long long)t->request.id);
}
static void transfer_ready(struct ll_queue* q, void* arg) { (void)q; transfer_wake(arg); }
static struct mf_message transfer_packet(struct mf_transfer* t, uint8_t command) {
struct mf_message q = { .kind = t->server ? MF_RESPONSE : MF_REQUEST, .command = command,
.request_id = t->request.id, .owner = t->request.owner, .offset = t->offset, .size = t->size };
snprintf(q.path, sizeof(q.path), "%s", t->request.remote);
memcpy(q.upload, t->upload, 16); memcpy(q.version, t->version, 16); memcpy(q.hash, t->hash, 32);
return q;
}
static struct mf_message transfer_storage_request(struct mf_transfer* t, uint8_t command) {
struct mf_message q = t->open;
q.command = command; q.kind = MF_REQUEST; q.data_len = 0;
return q;
}
static int transfer_allowed(struct mf_transfer* t) {
struct UTUN_INSTANCE* inst = t->files->inst;
if (!t->server) return member_files_member(inst, t->request.group, inst->node_id, 0) &&
member_files_member(inst, t->request.group, t->request.server, 1);
struct mf_message q = t->open;
return mf_server_allowed(t->files, t->request.group, t->request.server, &q);
}
static void transfer_release(struct mf_transfer* t) {
struct member_files* f = t->files;
if (t->pump) uasync_call_soon_cancel(f->inst->ua, t->pump);
etcp_router_cancel_send_ready(f->inst, t->request.group, t->request.server, ETCP_RT_ID_MEMBER_FILES, &t->waiter);
if (t->cm) conn_mgr_close(t->cm);
mf_worker_unreserve(t->cleanup);
struct mf_transfer** link = &f->transfers;
while (*link && *link != t) link = &(*link)->next;
if (*link) *link = t->next;
if (t->server) f->server_transfer_count--; else f->transfer_count--;
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: stream finished id=%llu server=%d sending=%d bytes=%llu/%llu status=%s",
(unsigned long long)t->request.id, t->server, t->sending, (unsigned long long)t->offset,
(unsigned long long)t->size, mf_status_text(t->status));
mf_transfer_fn callback = t->callback; void* arg = t->arg;
struct mf_transfer_progress progress = { t->request.id, t->offset, t->size, (uint16_t)t->status, 1 };
int server = t->server;
u_free(t);
mf_receive_resume(f);
if (!server && callback) callback(arg, &progress);
}
/* Worker читает только снимок io_*; uasync не меняет занятые этим снимком ячейки. */
static void transfer_work(void* arg) {
struct mf_transfer* t = arg;
int rc = MF_OK;
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: stream I/O id=%llu work=%d offset=%llu slots=%u",
(unsigned long long)t->request.id, t->work, (unsigned long long)t->io_offset, t->io_count);
if (t->work == TRANSFER_PREPARE) {
if (t->server) {
struct mf_message q = t->open;
q.command = t->sending ? MF_STAT : MF_BEGIN_WRITE;
mf_storage_execute(t->files->storage, t->request.group, t->request.server, &q, &t->result);
rc = t->result.status;
if (!rc && t->sending && (t->result.type != MF_FILE || t->result.size != t->size ||
memcmp(t->result.version, t->version, 16))) rc = MF_STALE;
} else if (t->sending) {
rc = mf_disk_local_open(t->request.local, 0, &t->fd);
if (!rc) rc = mf_disk_info(t->fd, &t->source);
if (!rc && (t->source.type != MF_FILE || t->source.size > INT64_MAX)) rc = MF_INVALID_RANGE;
if (!rc) { t->io_size = t->source.size; rc = mf_disk_hash(t->fd, t->io_size, t->hash); }
struct mf_disk_info after;
if (!rc) rc = mf_disk_info(t->fd, &after);
if (!rc && memcmp(&after, &t->source, sizeof(after))) rc = MF_STALE;
} else rc = mf_disk_local_open(t->temp, 1, &t->fd);
if (!rc && t->request.upload == 0) {
t->digest = EVP_MD_CTX_new();
if (!t->digest || EVP_DigestInit_ex(t->digest, EVP_sha256(), NULL) != 1) rc = MF_IO_ERROR;
}
} else if (t->work == TRANSFER_READ || t->work == TRANSFER_WRITE) {
uint64_t offset = t->io_offset; t->io_bytes = 0;
struct mf_disk_info info;
if (!t->server && t->sending) {
rc = mf_disk_info(t->fd, &info);
if (!rc && memcmp(&info, &t->source, sizeof(info))) rc = MF_STALE;
}
for (unsigned i = 0; !rc && i < t->io_count; i++) {
struct mf_chunk* chunk = &t->chunks[(t->io_head + i) % TRANSFER_SLOTS];
if (t->sending) chunk->bytes = (uint32_t)(t->size - offset < MF_BLOCK_SIZE ? t->size - offset : MF_BLOCK_SIZE);
if (t->server) {
struct mf_message q = transfer_storage_request(t, t->sending ? MF_READ : MF_WRITE);
q.kind = MF_REQUEST; q.offset = offset; q.size = chunk->bytes;
if (!t->sending) { q.data_len = chunk->bytes; memcpy(q.data, chunk->data, chunk->bytes); }
struct mf_message r;
mf_storage_execute(t->files->storage, t->request.group, t->request.server, &q, &r);
rc = r.status;
if (!rc && t->sending) {
if (r.data_len != chunk->bytes || r.size != t->size || memcmp(r.version, t->version, 16)) rc = MF_STALE;
else memcpy(chunk->data, r.data, r.data_len);
}
} else if (t->sending) rc = mf_disk_read(t->fd, offset, chunk->data, chunk->bytes);
else rc = mf_disk_write(t->fd, offset, chunk->data, chunk->bytes);
if (!rc && t->digest && EVP_DigestUpdate(t->digest, chunk->data, chunk->bytes) != 1) rc = MF_IO_ERROR;
if (!rc) { offset += chunk->bytes; t->io_bytes += chunk->bytes; }
}
} else if (t->work == TRANSFER_VERIFY) {
if (t->server && !t->sending) {
struct mf_message q = transfer_storage_request(t, MF_COMMIT);
mf_storage_execute(t->files->storage, t->request.group, t->request.server, &q, &t->result);
rc = t->result.status;
t->committed = rc == MF_OK;
} else {
if (t->digest) {
uint8_t hash[32]; unsigned bytes = 0;
if (EVP_DigestFinal_ex(t->digest, hash, &bytes) != 1 || bytes != 32) rc = MF_IO_ERROR;
else if (t->sending) memcpy(t->hash, hash, 32);
else if (memcmp(t->hash, hash, 32)) rc = MF_HASH_MISMATCH;
}
if (!rc && t->server) {
struct mf_message q = transfer_storage_request(t, MF_STAT);
mf_storage_execute(t->files->storage, t->request.group, t->request.server, &q, &t->result);
rc = t->result.status;
if (!rc && (t->result.size != t->size || memcmp(t->result.version, t->version, 16))) rc = MF_STALE;
} else if (!rc && t->sending) {
struct mf_disk_info after; rc = mf_disk_info(t->fd, &after);
if (!rc && memcmp(&after, &t->source, sizeof(after))) rc = MF_STALE;
}
}
} else {
if (t->server && !t->sending && t->status && !t->committed) {
struct mf_message q = transfer_storage_request(t, MF_ABORT);
mf_storage_execute(t->files->storage, t->request.group, t->request.server, &q, &t->result);
if (t->result.status) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: stream cleanup failed id=%llu status=%s",
(unsigned long long)t->request.id, mf_status_text(t->result.status));
}
if (t->fd >= 0) {
if (!t->status && !t->sending) rc = mf_disk_sync(t->fd);
int closed = mf_disk_close(t->fd); t->fd = -1;
if (!rc) rc = closed;
}
if (!t->server && !t->sending) {
if (!t->status && !rc) rc = mf_disk_local_publish(t->temp, t->request.local);
if (t->status || rc) mf_disk_local_remove(t->temp);
}
EVP_MD_CTX_free(t->digest); t->digest = NULL;
}
t->work_status = rc;
}
static void transfer_done(void* arg, int error) {
struct mf_transfer* t = arg; t->busy = 0;
int status = error ? MF_CANCELLED : t->work_status;
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: stream I/O done id=%llu work=%d status=%s bytes=%llu buffered=%u",
(unsigned long long)t->request.id, t->work, mf_status_text(status), (unsigned long long)t->io_bytes, t->count);
if (t->work == TRANSFER_FINISH) {
t->finished_io = 1;
if (!t->status) t->status = status;
if (t->files->closing || t->peer_down || (!t->server && !t->status)) { transfer_release(t); return; }
if (!t->server) {
t->outgoing = transfer_packet(t, MF_STREAM_CANCEL);
t->reply_ready = t->reply_final = 1; transfer_wake(t); return;
}
t->outgoing = t->opened ? transfer_packet(t, MF_STREAM_END) : t->open;
t->outgoing.kind = MF_RESPONSE; t->outgoing.data_len = 0; t->outgoing.status = (uint16_t)t->status;
t->reply_ready = t->reply_final = 1; transfer_wake(t); return;
}
if (t->stopping || t->files->closing || status) { transfer_stop(t, status ? status : MF_CANCELLED); return; }
t->progress_tb = get_time_tb();
if (t->work == TRANSFER_PREPARE) {
if (!t->server && t->sending) t->size = t->io_size;
if (t->server) {
t->outgoing = t->open; t->outgoing.kind = MF_RESPONSE; t->outgoing.data_len = 0;
t->outgoing.size = t->size; t->outgoing.status = MF_OK;
t->reply_ready = 1;
} else { transfer_send_open(t, t->sending ? MF_STREAM_WRITE : MF_STREAM_READ); return; }
} else if (t->work == TRANSFER_READ) {
t->read_offset += t->io_bytes; t->count += t->io_count;
} else if (t->work == TRANSFER_WRITE) {
t->offset += t->io_bytes; t->head = (t->head + t->io_count) % TRANSFER_SLOTS; t->count -= t->io_count;
mf_receive_resume(t->files);
} else {
t->verified = 1;
if (t->server || !t->sending) { transfer_stop(t, MF_OK); return; }
}
transfer_wake(t);
}
static void transfer_submit(struct mf_transfer* t, enum transfer_work work) {
t->work = work; t->busy = 1;
if (work == TRANSFER_FINISH) {
struct mf_work* cleanup = t->cleanup; t->cleanup = NULL;
mf_worker_submit_reserved(t->files->disk, cleanup, transfer_work, t, transfer_done, t);
} else mf_worker_submit(t->files->disk, transfer_work, t, transfer_done, t);
}
static void transfer_stop(struct mf_transfer* t, int status) {
if (t->stopping) {
if ((t->files->closing || status) && t->finished_io && !t->busy) transfer_release(t);
else if (!t->busy && !t->finished_io) transfer_submit(t, TRANSFER_FINISH);
return;
}
if (status && !t->status) {
t->status = status;
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: stream stopped id=%llu peer=%llx reason=%s",
(unsigned long long)t->request.id, (unsigned long long)t->request.server, mf_status_text(status));
}
t->stopping = 1;
if (t->pump) { uasync_call_soon_cancel(t->files->inst->ua, t->pump); t->pump = NULL; }
etcp_router_cancel_send_ready(t->files->inst, t->request.group, t->request.server, ETCP_RT_ID_MEMBER_FILES, &t->waiter);
if (t->pending) { struct mf_pending* p = t->pending; t->pending = NULL; member_files_cancel(p); }
if (t->busy) return;
if (t->finished_io) { transfer_release(t); return; }
transfer_submit(t, TRANSFER_FINISH);
}
static int transfer_enqueue(struct mf_transfer* t, struct mf_message* q) {
int rc = mf_send(t->files, t->request.group, t->request.server, q);
if (rc > 0 && t->request.server != t->files->inst->node_id)
etcp_router_on_send_ready(t->files->inst, t->request.group, t->request.server, ETCP_RT_ID_MEMBER_FILES,
&t->waiter, transfer_ready, t);
return rc;
}
static void transfer_pump(void* arg) {
struct mf_transfer* t = arg; t->pump = NULL;
if (t->files->closing) return;
if (t->starting) {
t->starting = 0;
if (t->sending || t->server) transfer_submit(t, TRANSFER_PREPARE);
else transfer_send_open(t, MF_STAT);
return;
}
if (t->reply_ready) {
if (!t->status && !transfer_allowed(t)) {
if (!t->finished_io) { transfer_stop(t, MF_ACCESS_DENIED); return; }
t->status = t->outgoing.status = MF_ACCESS_DENIED;
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: final stream reply denied id=%llu", (unsigned long long)t->request.id);
}
int rc = transfer_enqueue(t, &t->outgoing);
if (rc < 0) { transfer_stop(t, MF_IO_ERROR); return; }
if (rc > 0) return;
t->reply_ready = 0;
if (t->reply_final) { transfer_release(t); return; }
t->opened = t->active = 1;
}
if (!t->active || t->stopping) return;
if (!transfer_allowed(t)) { transfer_stop(t, MF_ACCESS_DENIED); return; }
if (t->sending) {
// Ограничиваем работу callback, сохраняя fair scheduling нескольких файлов.
unsigned sent = 0;
while (t->count && sent++ < 8) {
struct mf_chunk* chunk = &t->chunks[t->head];
struct mf_message q = transfer_packet(t, MF_STREAM_DATA);
q.size = q.data_len = chunk->bytes; memcpy(q.data, chunk->data, chunk->bytes);
int rc = transfer_enqueue(t, &q);
if (rc < 0) { transfer_stop(t, MF_IO_ERROR); return; }
if (rc > 0) break;
t->offset += chunk->bytes; t->head = (t->head + 1) % TRANSFER_SLOTS; t->count--;
t->progress_tb = get_time_tb();
}
if (!t->busy && t->read_offset < t->size && t->count <= TRANSFER_SLOTS - TRANSFER_BATCH) {
uint64_t blocks = (t->size - t->read_offset + MF_BLOCK_SIZE - 1) / MF_BLOCK_SIZE;
t->io_count = blocks < TRANSFER_BATCH ? (unsigned)blocks : TRANSFER_BATCH;
t->io_head = (t->head + t->count) % TRANSFER_SLOTS; t->io_offset = t->read_offset;
transfer_submit(t, TRANSFER_READ);
} else if (!t->busy && !t->count && t->offset == t->size && !t->verified) transfer_submit(t, TRANSFER_VERIFY);
if (t->verified && !t->end_sent) {
struct mf_message q = transfer_packet(t, MF_STREAM_END);
int rc = transfer_enqueue(t, &q);
if (rc < 0) { transfer_stop(t, MF_IO_ERROR); return; }
if (!rc) { t->end_sent = 1; t->progress_tb = get_time_tb(); }
}
if (t->count) transfer_wake(t);
} else if (!t->busy) {
if (t->count) {
t->io_head = t->head; t->io_offset = t->offset;
t->io_count = t->count < TRANSFER_BATCH ? t->count : TRANSFER_BATCH;
transfer_submit(t, TRANSFER_WRITE);
} else if (t->end_received) transfer_submit(t, TRANSFER_VERIFY);
}
transfer_event(t, 0);
}
static void transfer_response(void* arg, const struct mf_message* r) {
struct mf_transfer* t = arg; t->pending = NULL;
if (t->stopping) return;
if (r->status) { transfer_stop(t, r->status); return; }
if (r->command == MF_STAT) {
if (r->type != MF_FILE || r->size > INT64_MAX || mf_zero(r->version, 16)) { transfer_stop(t, MF_INVALID_RANGE); return; }
t->size = r->size; memcpy(t->version, r->version, 16); transfer_submit(t, TRANSFER_PREPARE);
} else if (r->size != t->size || r->cursor != t->request.id || memcmp(r->upload, t->upload, 16)) {
transfer_stop(t, MF_BAD_PACKET);
} else { t->opened = t->active = 1; t->progress_tb = get_time_tb(); transfer_wake(t); }
}
static void transfer_send_open(struct mf_transfer* t, uint8_t command) {
struct mf_message q = transfer_packet(t, command);
q.request_id = 0; q.cursor = command == MF_STAT ? 0 : t->request.id;
if (command == MF_STREAM_WRITE) memcpy(q.version, t->request.version, 16);
t->pending = member_files_request(t->files->inst, t->request.group, t->request.server, &q, transfer_response, t);
if (!t->pending) transfer_stop(t, MF_BUSY);
}
static void transfer_connected(struct CONN_MGR_HANDLE* h, uint64_t peer, uint64_t group, enum conn_mgr_event event, void* arg) {
(void)h; (void)peer; (void)group;
struct mf_transfer* t = arg;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: stream CM id=%llu event=%d", (unsigned long long)t->request.id, event);
if (event == CONN_EVENT_UP) transfer_wake(t);
}
static struct mf_transfer* transfer_create(struct member_files* f, const struct mf_transfer_request* q, int server) {
struct mf_transfer* t = u_calloc(1, sizeof(*t));
if (!t) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: stream allocation failed"); return NULL; }
t->cleanup = mf_worker_reserve();
if (!t->cleanup) { u_free(t); return NULL; }
t->files = f; t->request = *q; t->fd = -1; t->server = server; t->sending = server ? !q->upload : q->upload;
t->progress_tb = t->last_log = get_time_tb();
t->next = f->transfers; f->transfers = t;
if (server) f->server_transfer_count++; else f->transfer_count++;
return t;
}
struct mf_transfer* member_files_transfer_start(const struct mf_transfer_request* q, mf_transfer_fn callback, void* arg) {
struct member_files* f = q && q->inst ? q->inst->member_files : NULL;
if (!f || f->closing || !callback || f->transfer_count >= TRANSFER_MAX || !q->group || !q->server || !q->owner ||
!mf_path_valid(q->remote, 0) || !*q->local || strnlen(q->local, sizeof(q->local)) > MF_PATH_MAX ||
(q->upload && q->owner != q->inst->node_id) || !member_files_member(q->inst, q->group, q->inst->node_id, 0) ||
!member_files_member(q->inst, q->group, q->server, 1)) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: stream start denied/limit reached"); return NULL;
}
struct mf_transfer_request request = *q;
if (!request.id && (RAND_bytes((uint8_t*)&request.id, 8) != 1 || !request.id)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: stream ID random failed"); return NULL;
}
for (struct mf_transfer* other = f->transfers; other; other = other->next) if (!other->server &&
(other->request.id == request.id || (!q->upload && !other->request.upload && !strcmp(other->request.local, q->local)))) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: duplicate stream ID/download destination"); return NULL;
}
struct mf_transfer* t = transfer_create(f, &request, 0);
if (!t) return NULL;
if (RAND_bytes(t->upload, 16) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: stream random failed"); goto failed; }
snprintf(t->temp, sizeof(t->temp), "%s.utun-%016llx.part", q->local, (unsigned long long)request.id);
t->starting = 1;
if (q->server != q->inst->node_id && conn_mgr_open(q->inst, q->group, q->server, transfer_connected, t, &t->cm)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: stream CM launch failed"); goto failed;
}
transfer_wake(t);
if (!t->pump) goto failed;
t->callback = callback; t->arg = arg;
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: stream started id=%llu upload=%d remote=%s local=%s active=%u",
(unsigned long long)request.id, q->upload, q->remote, q->local, f->transfer_count);
return t;
failed:
t->status = MF_IO_ERROR; transfer_release(t); return NULL;
}
static struct mf_transfer* transfer_find(struct member_files* f, uint64_t group, uint64_t peer, uint64_t id, int server) {
for (struct mf_transfer* t = f->transfers; t; t = t->next)
if (t->server == server && t->request.group == group && t->request.server == peer && t->request.id == id) return t;
return NULL;
}
void mf_transfer_dispatch(struct member_files* f, uint64_t group, uint64_t peer, const struct mf_message* q) {
int server = q->kind == MF_REQUEST;
uint64_t id = q->command <= MF_STREAM_READ ? q->cursor : q->request_id;
struct mf_transfer* t = transfer_find(f, group, peer, id, server);
if (q->command == MF_STREAM_WRITE || q->command == MF_STREAM_READ) {
if (t) {
if (memcmp(&t->open, q, sizeof(*q))) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: conflicting stream open"); return; }
if (t->active && !t->stopping) { t->outgoing = t->open; t->outgoing.kind = MF_RESPONSE; t->reply_ready = 1; transfer_wake(t); }
return;
}
int status = !id || !q->owner || q->size > INT64_MAX || q->data_len || !mf_path_valid(q->path, 0) ? MF_BAD_PACKET : MF_OK;
if (!status && !mf_server_allowed(f, group, peer, q)) status = MF_ACCESS_DENIED;
if (!status && f->server_transfer_count >= TRANSFER_MAX) status = MF_BUSY;
if (status) { mf_reply(f, group, peer, q, status); return; }
struct mf_transfer_request request = { .inst = f->inst, .id = id, .group = group, .server = peer,
.owner = q->owner, .upload = q->command == MF_STREAM_WRITE };
snprintf(request.remote, sizeof(request.remote), "%s", q->path);
t = transfer_create(f, &request, 1);
if (!t) { mf_reply(f, group, peer, q, MF_BUSY); return; }
t->open = *q; t->size = q->size; memcpy(t->upload, q->upload, 16); memcpy(t->version, q->version, 16); memcpy(t->hash, q->hash, 32);
f->used_storage = 1;
t->starting = 1; transfer_wake(t);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: server stream opened id=%llu peer=%llx sending=%d bytes=%llu active=%u",
(unsigned long long)id, (unsigned long long)peer, t->sending, (unsigned long long)t->size, f->server_transfer_count);
return;
}
if (!t || t->stopping) {
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: late stream packet id=%llu command=%s",
(unsigned long long)id, mf_command_text(q->command));
return;
}
if (q->owner != t->request.owner || strcmp(q->path, t->request.remote) || memcmp(q->upload, t->upload, 16)) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: stream packet identity mismatch id=%llu", (unsigned long long)id);
transfer_stop(t, MF_BAD_PACKET); return;
}
if (q->status || q->command == MF_STREAM_CANCEL) { transfer_stop(t, q->status ? q->status : MF_CANCELLED); return; }
if (!transfer_allowed(t)) { transfer_stop(t, MF_ACCESS_DENIED); return; }
if (q->command == MF_STREAM_DATA) {
if (t->sending || !t->active || t->end_received || !q->data_len || q->size != q->data_len ||
q->offset != t->received || q->data_len > t->size - t->received || t->count == TRANSFER_SLOTS) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: stream DATA invalid id=%llu offset=%llu expected=%llu bytes=%u buffered=%u",
(unsigned long long)id, (unsigned long long)q->offset, (unsigned long long)t->received, q->data_len, t->count);
transfer_stop(t, MF_BAD_PACKET); return;
}
struct mf_chunk* chunk = &t->chunks[(t->head + t->count) % TRANSFER_SLOTS];
chunk->bytes = q->data_len; memcpy(chunk->data, q->data, q->data_len); t->count++; t->received += q->data_len;
} else if (q->command == MF_STREAM_END) {
if (q->data_len || q->size != t->size || q->offset != t->size ||
(t->sending ? !t->end_sent : t->received != t->size)) { transfer_stop(t, MF_BAD_PACKET); return; }
if (t->request.upload && memcmp(t->hash, q->hash, 32)) { transfer_stop(t, MF_HASH_MISMATCH); return; }
memcpy(t->hash, q->hash, 32); t->end_received = 1;
if (t->sending) { transfer_stop(t, MF_OK); return; }
}
t->progress_tb = get_time_tb(); transfer_wake(t);
}
int mf_transfer_receive_ready(struct member_files* f, uint64_t group, uint64_t peer, int kind, uint64_t id) {
struct mf_transfer* t = transfer_find(f, group, peer, id, kind == MF_REQUEST);
return !t || t->stopping || t->sending || t->count < TRANSFER_SLOTS;
}
void mf_transfers_wake(struct member_files* f) {
if (!f->closing) for (struct mf_transfer* t = f->transfers; t; t = t->next) if (!t->stopping || t->reply_ready) transfer_wake(t);
}
void mf_transfers_tick(struct member_files* f, uint64_t now) {
struct mf_transfer* t = f->transfers;
while (t) {
struct mf_transfer* next = t->next;
if (t->stopping && t->finished_io && now - t->progress_tb >= TRANSFER_TIMEOUT_TB) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: final/cancel delivery timeout id=%llu", (unsigned long long)t->request.id);
transfer_release(t);
} else if (!t->stopping && now - t->progress_tb >= TRANSFER_TIMEOUT_TB) transfer_stop(t, MF_TIMEOUT);
else if (!t->stopping && now - t->last_log >= 10000) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"member_files: stream progress id=%llu server=%d sending=%d done=%llu/%llu buffered=%u io=%d wait=%s rate=%lluB/s",
(unsigned long long)t->request.id, t->server, t->sending, (unsigned long long)t->offset,
(unsigned long long)t->size, t->count, t->busy, t->waiter.internal ? "router" : t->busy ? "disk" : "peer",
(unsigned long long)((t->offset - t->logged_offset) * 10000 / (now - t->last_log)));
t->last_log = now; t->logged_offset = t->offset;
}
t = next;
}
mf_transfers_wake(f);
}
void member_files_transfer_cancel(struct mf_transfer* t) { if (t && !t->stopping) transfer_stop(t, MF_CANCELLED); }
void mf_transfers_quiesce(struct member_files* f, uint64_t group) {
struct mf_transfer* t = f->transfers;
while (t) {
struct mf_transfer* next = t->next;
if (!group || t->request.group == group) { t->peer_down = 1; transfer_stop(t, MF_CANCELLED); }
t = next;
}
}
void mf_transfers_peer_down(struct member_files* f, uint64_t group, uint64_t peer) {
struct mf_transfer* t = f->transfers;
while (t) {
struct mf_transfer* next = t->next;
if (t->request.group == group && t->request.server == peer) { t->peer_down = 1; transfer_stop(t, MF_IO_ERROR); }
t = next;
}
}
static void transfer_gui_event(void* arg, const struct mf_transfer_progress* p) {
struct mf_transfer_request* q = arg;
uint8_t data[51];
mf_put64(data, p->id); mf_put64(data + 8, q->group); mf_put64(data + 16, q->server); mf_put64(data + 24, q->owner);
mf_put64(data + 32, p->done); mf_put64(data + 40, p->total);
data[48] = (uint8_t)p->status; data[49] = (uint8_t)(p->status >> 8); data[50] = p->finished ? 2 : 1;
chat_event_post(q->inst, CHAT_EVT_MEMBER_FILES_TRANSFER, data, sizeof(data));
if (p->finished) u_free(q);
}
void member_files_gui_transfer(void* arg) {
struct mf_transfer_request* q = arg;
if (!member_files_transfer_start(q, transfer_gui_event, q)) {
struct mf_transfer_progress p = { .id = q->id, .status = MF_BUSY, .finished = 1 };
transfer_gui_event(q, &p);
}
}
void member_files_gui_cancel(void* arg) {
struct mf_cancel_request* q = arg;
struct member_files* f = q->inst ? q->inst->member_files : NULL;
struct mf_transfer* t = f ? f->transfers : NULL;
while (t && (t->server || t->request.id != q->id)) t = t->next;
if (t) member_files_transfer_cancel(t);
else member_files_cancel_request_id(q->inst, q->id);
u_free(q);
}