Add a read API to the encrypted log: signal_log_recent + logdb-recent
ober
26e0187fc5688375eb87459577e2e302a8b1ce7a
--- a/signal/log_shim.c +++ b/signal/log_shim.c @@ -130,6 +130,52 @@ long long signal_log_count(void *handle) { return n; } +/* Begin reading the most recent displayable rows, newest first. Only data + * and edit rows with a body and a conversation qualify -- receipts, typing + * indicators and bare events are noise for history. Returns a statement + * handle (step/text/int/close below) or NULL. + * Columns: 0 direction, 1 conversation, 2 sender, 3 timestamp, 4 kind, 5 body. */ +void *signal_log_recent(void *handle, long long limit) { + if (!handle) return NULL; + sqlite3 *db = (sqlite3 *)handle; + static const char *SQL = + "SELECT direction, conversation, sender, timestamp, kind, body" + " FROM messages" + " WHERE kind IN ('data','edit')" + " AND direction IN ('in','out')" + " AND conversation IS NOT NULL AND conversation <> ''" + " AND body IS NOT NULL AND body <> ''" + " ORDER BY id DESC LIMIT ?;"; + sqlite3_stmt *st = NULL; + if (sqlite3_prepare_v2(db, SQL, -1, &st, NULL) != SQLITE_OK) + return NULL; + sqlite3_bind_int64(st, 1, (sqlite3_int64)(limit > 0 ? limit : 0)); + return (void *)st; +} + +/* 1 = a row is available, 0 = done (or error -- treated as done). */ +int signal_log_row_step(void *stmt) { + if (!stmt) return 0; + return sqlite3_step((sqlite3_stmt *)stmt) == SQLITE_ROW ? 1 : 0; +} + +/* Text column of the current row; "" for NULL. Valid until the next step. */ +const char *signal_log_row_text(void *stmt, int col) { + if (!stmt) return ""; + const unsigned char *s = sqlite3_column_text((sqlite3_stmt *)stmt, col); + return s ? (const char *)s : ""; +} + +long long signal_log_row_int(void *stmt, int col) { + if (!stmt) return 0; + return (long long)sqlite3_column_int64((sqlite3_stmt *)stmt, col); +} + +int signal_log_row_close(void *stmt) { + if (!stmt) return 0; + return sqlite3_finalize((sqlite3_stmt *)stmt); +} + /* Read a passphrase from the controlling terminal with echo disabled. * Returns a pointer to a static buffer (copied by the FFI into a Scheme string). */ const char *signal_log_getpass(const char *prompt) { @@ -156,6 +202,16 @@ int main(void) { /* Reopen with correct key: both rows (incl. the original) still present. */ void *db2 = signal_log_open(path, "correct horse battery staple"); long long n2 = db2 ? signal_log_count(db2) : -1; + + /* Readback: only the data row qualifies (remote-delete has no body). */ + int recent = 0, body_ok = 0; + void *st = db2 ? signal_log_recent(db2, 10) : NULL; + while (st && signal_log_row_step(st)) { + recent++; + body_ok = strcmp(signal_log_row_text(st, 5), "hello world") == 0 + && signal_log_row_int(st, 3) == 1717000000000LL; + } + if (st) signal_log_row_close(st); if (db2) signal_log_close(db2); /* Wrong key must fail. */ @@ -163,8 +219,8 @@ int main(void) { int wrong_rejected = (db3 == NULL); if (db3) signal_log_close(db3); - printf("rows=%lld reopened=%lld wrong_key_rejected=%d\n", - n, n2, wrong_rejected); - return (n == 2 && n2 == 2 && wrong_rejected) ? 0 : 2; + printf("rows=%lld reopened=%lld recent=%d body_ok=%d wrong_key_rejected=%d\n", + n, n2, recent, body_ok, wrong_rejected); + return (n == 2 && n2 == 2 && recent == 1 && body_ok && wrong_rejected) ? 0 : 2; } #endif --- a/signal/logdb.ss +++ b/signal/logdb.ss @@ -7,7 +7,7 @@ (library (signal logdb) (export logdb-available? - logdb-open logdb-close logdb-put logdb-count + logdb-open logdb-close logdb-put logdb-count logdb-recent logdb-prompt-passphrase) (import (except (chezscheme) @@ -84,6 +84,40 @@ ((foreign-procedure "signal_log_count" (uptr) integer-64) handle) -1)) + ;; The most recent displayable rows (data/edit with a body), oldest first. + ;; Each row is (direction conversation sender timestamp kind body) -- all + ;; strings except timestamp (integer ms, 0 when unknown). '() when the shim + ;; is missing, the handle is #f, or anything goes wrong: history is + ;; best-effort and must never block the TUI from starting. + (def (logdb-recent handle limit) + (if (and handle + (entry? "signal_log_recent") + (entry? "signal_log_row_step") + (entry? "signal_log_row_text") + (entry? "signal_log_row_int") + (entry? "signal_log_row_close")) + (guard (e [#t '()]) + (let ([open (foreign-procedure "signal_log_recent" (uptr integer-64) uptr)] + [step (foreign-procedure "signal_log_row_step" (uptr) int)] + [text (foreign-procedure "signal_log_row_text" (uptr int) string)] + [num (foreign-procedure "signal_log_row_int" (uptr int) integer-64)] + [close (foreign-procedure "signal_log_row_close" (uptr) int)]) + (let ([st (open handle limit)]) + (if (= st 0) + '() + (dynamic-wind + (lambda () (void)) + (lambda () + ;; Rows arrive newest-first; consing reverses to oldest-first. + (let loop ([acc '()]) + (if (= (step st) 1) + (loop (cons (list (text st 0) (text st 1) (text st 2) + (num st 3) (text st 4) (text st 5)) + acc)) + acc))) + (lambda () (close st))))))) + '())) + ;; Read a passphrase from the controlling terminal with echo off. (def (logdb-prompt-passphrase prompt) (if (entry? "signal_log_getpass")