dm: define durable storage request identities and scoped source expiry
This commit is contained in:
1 parent
3d4dd893b6
commit
75ab57798e
3 files changed
+23
-10
No files matched your search
@@ -3,7 +3,8 @@
|
||||
* Жизненный цикл принадлежит dm_mailbox, control принимает dm_media. Все API в uasync.
|
||||
* Координатор хранится в dm_media_jobs/dm_media_holders SQLite ядра; таймер — только триггер повторов.
|
||||
* Таймер 1с обрабатывает до 8 задач, retry_at через 5с; выбранный M сохраняется до STORE.
|
||||
* Выбор M: storage=yes в общей группе A/B, достижимый узел по node_id, кроме A/B и прежних назначений.
|
||||
* Выбор M: storage=yes в допущенной общей группе S/M, маршрут до M выбирается независимо.
|
||||
* assignment_id сохраняется до STORE и не меняется при смене маршрута/рестарте.
|
||||
* S может одновременно быть M: local control проходит тот же обработчик без router-соединения к себе.
|
||||
* SQLite и группы принадлежат ядру; instance должен пережить owner. Входные тела заимствованы до возврата.
|
||||
* Из C++ этот внутренний C API включать внутри extern "C".
|
||||
@@ -27,14 +28,14 @@ void dm_mailbox_media_destroy(struct dm_mb_media* media);
|
||||
int dm_mailbox_media_register(struct dm_mb_media* media, uint64_t group, uint64_t recipient,
|
||||
const uint8_t* msg, size_t len);
|
||||
/* После проверки router-подписи и размера команды в dm_media: group/source — доверенные поля router.
|
||||
* body заимствован на время вызова; RECEIPT=152, STORED=24, DELETE_ACK/EXPIRED=16, REJECT=17 байт.
|
||||
* RECEIPT имеет формат dm_media.h; STORED=[UUID:16][expires:8], ACK/EXPIRED=[UUID:16], REJECT=[UUID:16][reason:1].
|
||||
* body заимствован на время вызова; RECEIPT=152, STORED/RESERVED=41, DELETE_ACK/EXPIRED=32, REJECT=33 байта.
|
||||
* Форматы — dm_media.h; каждый ответ M содержит сохранённые assignment_id и UUID.
|
||||
* STORED/REJECT/ACK относятся к сохранённому назначению M; RECEIPT проверяется отдельно подписью B.
|
||||
* EXPIRED/REJECT атомарно освобождают назначение и сбрасывают текущий holder; ошибка откатывает оба изменения.
|
||||
* Таймер завершает stored-назначение по expires даже при offline B/потерянном EXPIRED, также после restart.
|
||||
* После квитанции повторяется DELETE до 16 неосвобождённым M за проход cleanup.
|
||||
* Тело удаляется только при отсутствии неосвобождённых назначений; ошибка сканирования откладывает очистку.
|
||||
* Для неподтверждённого STORE повтор запрашивает результат из custody/tombstone M; квитанция B не отменяется. */
|
||||
* STORE повторно запрашивает результат из custody/tombstone M, также после STORED; квитанция B не отменяется. */
|
||||
void dm_mailbox_media_recv(struct dm_mb_media* media, uint64_t group, uint64_t source,
|
||||
uint8_t cmd, const uint8_t* body, size_t len);
|
||||
/* Проверить 152-байтовую квитанцию и попытаться отправить доступным S общей группы.
|
||||
|
||||
+10
-5
@@ -20,6 +20,7 @@ struct UTUN_INSTANCE;
|
||||
|
||||
#define DM_MEDIA_DESC_SIZE 56 /* UUID:16, plaintext_size:8 native-endian, SHA256(ciphertext):32. */
|
||||
#define DM_MEDIA_RECEIPT_SIZE 152
|
||||
#define DM_MEDIA_ASSIGNMENT_SIZE 16 /* Постоянный ID запроса S->M; повтор/смена маршрута сохраняют его. */
|
||||
#define DM_MEDIA_MAX_SIZE (1024ULL * 1024 * 1024)
|
||||
#define DM_MEDIA_KEYS_HEADER_SIZE 232 /* "DMKEY001" и DM_FILE_KEYS_SIZE байт. */
|
||||
|
||||
@@ -30,12 +31,15 @@ int dm_media_metadata_decode(const uint8_t* data, size_t size, struct attachment
|
||||
|
||||
/* ETCP_RT_ID_DM_MEDIA=0x38: [cmd:1][body], router-подпись обязательна; числа native-endian.
|
||||
* OFFER: [recipient:8][holder:8][expires:8][каноническое file-сообщение]. A предлагает себя с expires=0.
|
||||
* STORE: [recipient:8][file-сообщение] от S к M; STORED/RESERVED: [UUID:16][expires:8][delete_on_open:1].
|
||||
* RECEIPT/DELETE: [domain:8][conv:8][seq:8][author:8][device:8][UUID:16][SHA256(message):32][sig:64].
|
||||
* STORE: [assignment:16][recipient:8][file-сообщение] от S к M.
|
||||
* STORED/RESERVED: [assignment:16][UUID:16][expires:8][delete_on_open:1].
|
||||
* RECEIPT: [domain:8][conv:8][seq:8][author:8][device:8][UUID:16][SHA256(message):32][sig:64].
|
||||
* DELETE: [assignment:16][RECEIPT:152]; назначение не заменяет подпись факта получателя.
|
||||
* FILE_RECEIVED: DMFILE01/DMFILEG1; TAKEN: DMTAKE01/DMTAKEG1 для DEVICE/GROUPS.
|
||||
* Устройство подписывает первые88 байт; DELETE переносит проверенный факт, M применяет свою сохранённую политику.
|
||||
* DELETE_ACK/EXPIRED: [UUID:16]; REJECT: [UUID:16][reason:1], reason=1 — недостаток квоты.
|
||||
* EXPIRED завершает промежуточное хранение, а не подтверждает доставку B. */
|
||||
* DELETE_ACK/EXPIRED: [assignment:16][UUID:16]; REJECT: [assignment:16][UUID:16][reason:1], reason=1 — квота.
|
||||
* SOURCE_GONE: [UUID:16][SHA256(message):32][holder:8] от S; прекращает использование только этого источника.
|
||||
* EXPIRED относится только к назначению S/M. Получателю не объявляется удаление личного файла. */
|
||||
#define DM_MEDIA_OFFER 1
|
||||
#define DM_MEDIA_STORE 2
|
||||
#define DM_MEDIA_STORED 3
|
||||
@@ -44,9 +48,10 @@ int dm_media_metadata_decode(const uint8_t* data, size_t size, struct attachment
|
||||
#define DM_MEDIA_DELETE_ACK 6
|
||||
#define DM_MEDIA_EXPIRED 7
|
||||
#define DM_MEDIA_REJECT 8
|
||||
#define DM_MEDIA_RESERVED 9 /* UUID:16, expires:8, delete_on_open:1; ciphertext ещё не подтверждён. */
|
||||
#define DM_MEDIA_RESERVED 9 /* assignment:16, UUID:16, expires:8, delete_on_open:1; ciphertext ещё не подтверждён. */
|
||||
#define DM_MEDIA_HAVE 10 /* UUID:16, SHA256:32, probe_id:8, cipher_size:8. */
|
||||
#define DM_MEDIA_HAVE_ACK 11 /* Точное эхо HAVE; RTT измеряет запросившее устройство. */
|
||||
#define DM_MEDIA_SOURCE_GONE 12
|
||||
|
||||
/* После dm_core_init с SQLite/config/router/ua и media_async ядра: таблицы, каталоги, file_transfer, bind и таймер.
|
||||
* 0=успех, -1=ошибка, в том числе повторный init живого owner. При отказе созданные таблицы/каталоги остаются.
|
||||
|
||||
+8
-1
@@ -55,12 +55,19 @@ int dm_media_schema_init(struct UTUN_INSTANCE* inst) {
|
||||
"supernode INTEGER NOT NULL,expires INTEGER NOT NULL,ready INTEGER NOT NULL DEFAULT 0,"
|
||||
"receipt BLOB,retry_at INTEGER NOT NULL DEFAULT 0);"
|
||||
"CREATE TABLE IF NOT EXISTS dm_custody_done(id BLOB PRIMARY KEY,body_hash BLOB NOT NULL,receipt BLOB);"
|
||||
"CREATE TABLE IF NOT EXISTS dm_custody_requests(supernode INTEGER NOT NULL,assignment_id BLOB NOT NULL,"
|
||||
"id BLOB NOT NULL,body_hash BLOB NOT NULL,rejected INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(supernode,assignment_id));"
|
||||
"CREATE TABLE IF NOT EXISTS dm_media_jobs(id BLOB PRIMARY KEY,author INTEGER NOT NULL,recipient INTEGER NOT NULL,"
|
||||
"seq INTEGER NOT NULL,body BLOB,body_hash BLOB NOT NULL,holder INTEGER NOT NULL DEFAULT 0,"
|
||||
"stored INTEGER NOT NULL DEFAULT 0,expires INTEGER NOT NULL DEFAULT 0,expired INTEGER NOT NULL DEFAULT 0,"
|
||||
"receipt BLOB,retry_at INTEGER NOT NULL DEFAULT 0);"
|
||||
"CREATE TABLE IF NOT EXISTS dm_media_holders(id BLOB NOT NULL,node_id INTEGER NOT NULL,"
|
||||
"released INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(id,node_id))")) goto fail;
|
||||
"assignment_id BLOB NOT NULL,released INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(id,node_id))")) goto fail;
|
||||
found = schema_has_column(db, "dm_media_holders", "assignment_id");
|
||||
if (found < 0) goto fail;
|
||||
if (!found && (schema_exec(db, "ALTER TABLE dm_media_holders ADD COLUMN assignment_id BLOB") ||
|
||||
schema_exec(db, "UPDATE dm_media_holders SET assignment_id=randomblob(16)"))) goto fail;
|
||||
if (schema_exec(db, "CREATE UNIQUE INDEX IF NOT EXISTS dm_media_assignment_id ON dm_media_holders(assignment_id)")) goto fail;
|
||||
const char* tables[] = {"dm_custody", "dm_custody_done", "dm_media_jobs"};
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
found = schema_has_column(db, tables[i], "delete_on_open");
|
||||
|
||||
Reference in new issue
Block a user