nostrdb

an unfairly fast embedded nostr database backed by lmdb
git clone git://jb55.com/nostrdb
Log | Files | Refs | Submodules | README | LICENSE

nostrdb.c (231717B)


      1 
      2 #include "nostrdb.h"
      3 #include "jsmn.h"
      4 #include "hex.h"
      5 #include "cursor.h"
      6 #include "random.h"
      7 #include "ccan/crypto/sha256/sha256.h"
      8 #include "bolt11/bolt11.h"
      9 #include "bolt11/amount.h"
     10 #include "lmdb.h"
     11 #include "metadata.h"
     12 #include "util.h"
     13 #include "cpu.h"
     14 #include "block.h"
     15 #include "threadpool.h"
     16 #include "thread.h"
     17 #include "protected_queue.h"
     18 #include "memchr.h"
     19 #include "print_util.h"
     20 #include <stdlib.h>
     21 #include <limits.h>
     22 #include <assert.h>
     23 
     24 #include "bindings/c/profile_json_parser.h"
     25 #include "bindings/c/profile_builder.h"
     26 #include "bindings/c/meta_builder.h"
     27 #include "bindings/c/meta_reader.h"
     28 #include "bindings/c/profile_verifier.h"
     29 #include "secp256k1.h"
     30 #include "secp256k1_ecdh.h"
     31 #include "secp256k1_schnorrsig.h"
     32 
     33 #define max(a,b) ((a) > (b) ? (a) : (b))
     34 #define min(a,b) ((a) < (b) ? (a) : (b))
     35 
     36 // the maximum number of things threads pop and push in bulk
     37 #define THREAD_QUEUE_BATCH 4096
     38 
     39 // maximum number of active subscriptions
     40 #define MAX_SUBSCRIPTIONS 256
     41 #define MAX_SCAN_CURSORS 12
     42 #define MAX_FILTERS    16
     43 
     44 // the maximum size of inbox queues
     45 static const int DEFAULT_QUEUE_SIZE = 32768;
     46 
     47 // 2mb scratch size for the writer thread
     48 static const int DEFAULT_WRITER_SCRATCH_SIZE = 2097152;
     49 
     50 // increase if we need bigger filters
     51 #define NDB_FILTER_PAGES 64
     52 
     53 #define ndb_flag_set(flags, f) ((flags & f) == f)
     54 
     55 #define NDB_PARSED_ID           (1 << 0)
     56 #define NDB_PARSED_PUBKEY       (1 << 1)
     57 #define NDB_PARSED_SIG          (1 << 2)
     58 #define NDB_PARSED_CREATED_AT   (1 << 3)
     59 #define NDB_PARSED_KIND         (1 << 4)
     60 #define NDB_PARSED_CONTENT      (1 << 5)
     61 #define NDB_PARSED_TAGS         (1 << 6)
     62 #define NDB_PARSED_ALL          (NDB_PARSED_ID|NDB_PARSED_PUBKEY|NDB_PARSED_SIG|NDB_PARSED_CREATED_AT|NDB_PARSED_KIND|NDB_PARSED_CONTENT|NDB_PARSED_TAGS)
     63 
     64 typedef int (*ndb_migrate_fn)(struct ndb_txn *);
     65 typedef int (*ndb_word_parser_fn)(void *, const char *word, int word_len,
     66 				  int word_index);
     67 
     68 /* parsed nip10 reply data */
     69 struct ndb_note_reply {
     70 	unsigned char *root;
     71 	unsigned char *reply;
     72 	unsigned char *mention;
     73 };
     74 
     75 // these must be byte-aligned, they are directly accessing the serialized data
     76 // representation
     77 #pragma pack(push, 1)
     78 
     79 union ndb_packed_str {
     80 	struct {
     81 		char str[3];
     82 		// we assume little endian everywhere. sorry not sorry.
     83 		unsigned char flag; // NDB_PACKED_STR, etc
     84 	} packed;
     85 
     86 	uint32_t offset;
     87 	unsigned char bytes[4];
     88 };
     89 
     90 struct ndb_tag {
     91 	uint16_t count;
     92 	union ndb_packed_str strs[0];
     93 };
     94 
     95 struct ndb_tags {
     96 	uint16_t padding;
     97 	uint16_t count;
     98 };
     99 
    100 // v1
    101 struct ndb_note {
    102 	unsigned char version;    // v=1
    103 	unsigned char padding[3]; // keep things aligned
    104 	unsigned char id[32];
    105 	unsigned char pubkey[32];
    106 	unsigned char sig[64];
    107 
    108 	uint64_t created_at;
    109 	uint32_t kind;
    110 	uint32_t content_length;
    111 	union ndb_packed_str content;
    112 	uint32_t strings;
    113 	// nothing can come after tags since it contains variadic data
    114 	struct ndb_tags tags;
    115 };
    116 
    117 #pragma pack(pop)
    118 
    119 
    120 struct ndb_migration {
    121 	ndb_migrate_fn fn;
    122 };
    123 
    124 struct ndb_profile_record_builder {
    125 	flatcc_builder_t *builder;
    126 	void *flatbuf;
    127 };
    128 
    129 // controls whether to continue or stop the json parser
    130 enum ndb_idres {
    131 	NDB_IDRES_CONT,
    132 	NDB_IDRES_STOP,
    133 };
    134 
    135 // closure data for the id-detecting ingest controller
    136 struct ndb_ingest_controller
    137 {
    138 	MDB_txn *read_txn;
    139 	struct ndb_lmdb *lmdb;
    140 	struct ndb_note *note;
    141 	uint64_t note_key;
    142 };
    143 
    144 enum ndb_writer_msgtype {
    145 	NDB_WRITER_QUIT, // kill thread immediately
    146 	NDB_WRITER_NOTE, // write a note to the db
    147 	NDB_WRITER_PROFILE, // write a profile to the db
    148 	NDB_WRITER_DBMETA, // write ndb metadata
    149 	NDB_WRITER_PROFILE_LAST_FETCH, // when profiles were last fetched
    150 	NDB_WRITER_BLOCKS, // write parsed note blocks
    151 	NDB_WRITER_MIGRATE, // migrate the database
    152 	NDB_WRITER_NOTE_RELAY, // we already have the note, but we have more relays to write
    153 	NDB_WRITER_NOTE_META, // write note metadata to the db
    154 };
    155 
    156 // keys used for storing data in the NDB metadata database (NDB_DB_NDB_META)
    157 enum ndb_meta_key {
    158 	NDB_META_KEY_VERSION = 1
    159 };
    160 
    161 struct ndb_json_parser {
    162 	const char *json;
    163 	int json_len;
    164 	struct ndb_builder builder;
    165 	jsmn_parser json_parser;
    166 	jsmntok_t *toks, *toks_end;
    167 	int i;
    168 	int num_tokens;
    169 };
    170 
    171 // useful to pass to threads on its own
    172 struct ndb_lmdb {
    173 	MDB_env *env;
    174 	MDB_dbi dbs[NDB_DBS];
    175 };
    176 
    177 struct ndb_writer {
    178 	struct ndb_lmdb *lmdb;
    179 	struct ndb_monitor *monitor;
    180 
    181 	int scratch_size;
    182 	uint32_t ndb_flags;
    183 	void *queue_buf;
    184 	int queue_buflen;
    185 	pthread_t thread_id;
    186 
    187 	struct prot_queue inbox;
    188 };
    189 
    190 struct ndb_ingester {
    191 	struct ndb_lmdb *lmdb;
    192 	uint32_t flags;
    193 	struct threadpool tp;
    194 	struct prot_queue *writer_inbox;
    195 	void *filter_context;
    196 	ndb_ingest_filter_fn filter;
    197 
    198 	int scratch_size;
    199 };
    200 
    201 struct ndb_filter_group {
    202 	struct ndb_filter filters[MAX_FILTERS];
    203 	int num_filters;
    204 };
    205 
    206 struct ndb_subscription {
    207 	uint64_t subid;
    208 	struct ndb_filter_group group;
    209 	struct prot_queue inbox;
    210 };
    211 
    212 struct ndb_monitor {
    213 	struct ndb_subscription subscriptions[MAX_SUBSCRIPTIONS];
    214 	ndb_sub_fn sub_cb;
    215 	void *sub_cb_ctx;
    216 	int num_subscriptions;
    217 
    218 	// monitor isn't a full inbox. We want pollers to be able to poll
    219 	// subscriptions efficiently without going through a message queue, so
    220 	// we use a simple mutex here.
    221 	pthread_mutex_t mutex;
    222 };
    223 
    224 struct ndb {
    225 	struct ndb_lmdb lmdb;
    226 	struct ndb_ingester ingester;
    227 	struct ndb_monitor monitor;
    228 	struct ndb_writer writer;
    229 	int version;
    230 	uint32_t flags; // setting flags
    231 	// lmdb environ handles, etc
    232 };
    233 
    234 ///
    235 /// Query Plans
    236 ///
    237 /// There are general strategies for performing certain types of query
    238 /// depending on the filter. For example, for large contact list queries
    239 /// with many authors, we simply do a descending scan on created_at
    240 /// instead of doing 1000s of pubkey scans.
    241 ///
    242 /// Query plans are calculated from filters via `ndb_filter_plan`
    243 ///
    244 enum ndb_query_plan {
    245 	NDB_PLAN_KINDS,
    246 	NDB_PLAN_IDS,
    247 	NDB_PLAN_AUTHORS,
    248 	NDB_PLAN_AUTHOR_KINDS,
    249 	NDB_PLAN_CREATED,
    250 	NDB_PLAN_TAGS,
    251 	NDB_PLAN_SEARCH,
    252 	NDB_PLAN_RELAY_KINDS,
    253 	NDB_PLAN_PROFILE_SEARCH,
    254 };
    255 
    256 // A id + u64 + timestamp
    257 struct ndb_id_u64_ts {
    258 	unsigned char id[32]; // pubkey, id, etc
    259 	uint64_t u64; // kind, etc
    260 	uint64_t timestamp;
    261 };
    262 
    263 // A clustered key with an id and a timestamp
    264 struct ndb_tsid {
    265 	unsigned char id[32];
    266 	uint64_t timestamp;
    267 };
    268 
    269 // A u64 + timestamp id. Just using this for kinds at the moment.
    270 struct ndb_u64_ts {
    271 	uint64_t u64; // kind, etc
    272 	uint64_t timestamp;
    273 };
    274 
    275 struct ndb_word
    276 {
    277 	const char *word;
    278 	int word_len;
    279 };
    280 
    281 struct ndb_search_words
    282 {
    283 	struct ndb_word words[MAX_TEXT_SEARCH_WORDS];
    284 	int num_words;
    285 };
    286 
    287 
    288 static inline int is_replaceable_kind(uint64_t kind)
    289 {
    290 	return kind == 0 || kind == 3
    291 		|| (10000 <= kind && kind < 20000)
    292 		|| (30000 <= kind && kind < 40000);
    293 }
    294 
    295 
    296 // ndb_text_search_key
    297 //
    298 // This is compressed when in lmdb:
    299 //
    300 //   note_id:    varint
    301 //   strlen:     varint
    302 //   str:        cstr
    303 //   timestamp:  varint
    304 //   word_index: varint
    305 //
    306 static int ndb_make_text_search_key(unsigned char *buf, int bufsize,
    307 				    int word_index, int word_len, const char *str,
    308 				    uint64_t timestamp, uint64_t note_id,
    309 				    int *keysize)
    310 {
    311 	struct cursor cur;
    312 	make_cursor(buf, buf + bufsize, &cur);
    313 
    314 	// TODO: need update this to uint64_t
    315 	// we push this first because our query function can pull this off
    316 	// quickly to check matches
    317 	if (!cursor_push_varint(&cur, (int32_t)note_id))
    318 		return 0;
    319 
    320 	// string length
    321 	if (!cursor_push_varint(&cur, word_len))
    322 		return 0;
    323 
    324 	// non-null terminated, lowercase string
    325 	if (!cursor_push_lowercase(&cur, str, word_len))
    326 		return 0;
    327 
    328 	// TODO: need update this to uint64_t
    329 	if (!cursor_push_varint(&cur, (int)timestamp))
    330 		return 0;
    331 
    332 	// the index of the word in the content so that we can do more accurate
    333 	// phrase searches
    334 	if (!cursor_push_varint(&cur, word_index))
    335 		return 0;
    336 
    337 	// pad to 8-byte alignment
    338 	if (!cursor_align(&cur, 8))
    339 		return 0;
    340 
    341 	*keysize = cur.p - cur.start;
    342 	assert((*keysize % 8) == 0);
    343 
    344 	return 1;
    345 }
    346 
    347 static int ndb_make_noted_text_search_key(unsigned char *buf, int bufsize,
    348 					  int wordlen, const char *word,
    349 					  uint64_t timestamp, uint64_t note_id,
    350 					  int *keysize)
    351 {
    352 	return ndb_make_text_search_key(buf, bufsize, 0, wordlen, word,
    353 					timestamp, note_id, keysize);
    354 }
    355 
    356 static int ndb_make_text_search_key_low(unsigned char *buf, int bufsize,
    357 					int wordlen, const char *word,
    358 					uint64_t since,
    359 					int *keysize)
    360 {
    361 	uint64_t note_id;
    362 	note_id = 0;
    363 	return ndb_make_text_search_key(buf, bufsize, 0, wordlen, word,
    364 					since, note_id, keysize);
    365 }
    366 
    367 static int ndb_make_text_search_key_high(unsigned char *buf, int bufsize,
    368 					 int wordlen, const char *word,
    369 					 uint64_t until,
    370 					 int *keysize)
    371 {
    372 	uint64_t note_id;
    373 	note_id = INT32_MAX;
    374 	return ndb_make_text_search_key(buf, bufsize, 0, wordlen, word,
    375 					until, note_id, keysize);
    376 }
    377 
    378 typedef int (*ndb_text_search_key_order_fn)(unsigned char *buf, int bufsize, int wordlen, const char *word, uint64_t timestamp, int *keysize);
    379 
    380 /** From LMDB: Compare two items lexically */
    381 static int mdb_cmp_memn(const MDB_val *a, const MDB_val *b) {
    382 	int diff;
    383 	ssize_t len_diff;
    384 	unsigned int len;
    385 
    386 	len = a->mv_size;
    387 	len_diff = (ssize_t) a->mv_size - (ssize_t) b->mv_size;
    388 	if (len_diff > 0) {
    389 		len = b->mv_size;
    390 		len_diff = 1;
    391 	}
    392 
    393 	diff = memcmp(a->mv_data, b->mv_data, len);
    394 	return diff ? diff : len_diff<0 ? -1 : len_diff;
    395 }
    396 
    397 static int ndb_tag_key_compare(const MDB_val *a, const MDB_val *b)
    398 {
    399 	MDB_val va, vb;
    400 	uint64_t ts_a, ts_b;
    401 	int cmp;
    402 
    403 	va.mv_data = a->mv_data;
    404 	va.mv_size = a->mv_size - 8;
    405 
    406 	vb.mv_data = b->mv_data;
    407 	vb.mv_size = b->mv_size - 8;
    408 
    409 	if ((cmp = mdb_cmp_memn(&va, &vb)))
    410 		return cmp;
    411 
    412 	ts_a = *(uint64_t*)((unsigned char *)va.mv_data + va.mv_size);
    413 	ts_b = *(uint64_t*)((unsigned char *)vb.mv_data + vb.mv_size);
    414 
    415 	if (ts_a < ts_b)
    416 		return -1;
    417 	else if (ts_a > ts_b)
    418 		return 1;
    419 
    420 	return 0;
    421 }
    422 
    423 static int ndb_text_search_key_compare(const MDB_val *a, const MDB_val *b)
    424 {
    425 	struct cursor ca, cb;
    426 	uint64_t sa, sb, nid_a, nid_b;
    427 	MDB_val a2, b2;
    428 
    429 	make_cursor(a->mv_data, (unsigned char *)a->mv_data + a->mv_size, &ca);
    430 	make_cursor(b->mv_data, (unsigned char *)b->mv_data + b->mv_size, &cb);
    431 
    432 	// note_id
    433 	if (unlikely(!cursor_pull_varint(&ca, &nid_a) || !cursor_pull_varint(&cb, &nid_b)))
    434 		return 0;
    435 
    436 	// string size
    437 	if (unlikely(!cursor_pull_varint(&ca, &sa) || !cursor_pull_varint(&cb, &sb)))
    438 		return 0;
    439 
    440 	a2.mv_data = ca.p;
    441 	a2.mv_size = sa;
    442 
    443 	b2.mv_data = cb.p;
    444 	b2.mv_size = sb;
    445 
    446 	int cmp = mdb_cmp_memn(&a2, &b2);
    447 	if (cmp) return cmp;
    448 
    449 	// skip over string
    450 	ca.p += sa;
    451 	cb.p += sb;
    452 
    453 	// timestamp
    454 	if (unlikely(!cursor_pull_varint(&ca, &sa) || !cursor_pull_varint(&cb, &sb)))
    455 		return 0;
    456 
    457 	if      (sa < sb) return -1;
    458 	else if (sa > sb) return 1;
    459 
    460 	// note_id
    461 	if      (nid_a < nid_b) return -1;
    462 	else if (nid_a > nid_b) return 1;
    463 
    464 	// word index
    465 	if (unlikely(!cursor_pull_varint(&ca, &sa) || !cursor_pull_varint(&cb, &sb)))
    466 		return 0;
    467 
    468 	if      (sa < sb) return -1;
    469 	else if (sa > sb) return 1;
    470 
    471 	return 0;
    472 }
    473 
    474 static inline int ndb_unpack_text_search_key_noteid(
    475 		struct cursor *cur, uint64_t *note_id)
    476 {
    477 	if (!cursor_pull_varint(cur, note_id))
    478 		return 0;
    479 
    480 	return 1;
    481 }
    482 
    483 // faster peek of just the string instead of unpacking everything
    484 // this is used to quickly discard range query matches if there is no
    485 // common prefix
    486 static inline int ndb_unpack_text_search_key_string(struct cursor *cur,
    487 						    const char **str,
    488 						    int *str_len)
    489 {
    490 	uint64_t len;
    491 
    492 	if (!cursor_pull_varint(cur, &len))
    493 		return 0;
    494 
    495 	*str_len = len;
    496 
    497 	*str = (const char *)cur->p;
    498 
    499 	if (!cursor_skip(cur, *str_len))
    500 		return 0;
    501 
    502 	return 1;
    503 }
    504 
    505 // should be called after ndb_unpack_text_search_key_string. It continues
    506 // the unpacking of a text search key if we've already started it.
    507 static inline int
    508 ndb_unpack_remaining_text_search_key(struct cursor *cur,
    509 				     struct ndb_text_search_key *key)
    510 {
    511 	if (!cursor_pull_varint(cur, &key->timestamp))
    512 		return 0;
    513 
    514 	if (!cursor_pull_varint(cur, &key->word_index))
    515 		return 0;
    516 
    517 	return 1;
    518 }
    519 
    520 // unpack a fulltext search key
    521 //
    522 // full version of string + unpack remaining. This is split up because text
    523 // searching only requires to pull the string for prefix searching, and the
    524 // remaining is optional
    525 static inline int ndb_unpack_text_search_key(unsigned char *p, int len,
    526 				      struct ndb_text_search_key *key)
    527 {
    528 	struct cursor c;
    529 	make_cursor(p, p + len, &c);
    530 
    531 	if (!ndb_unpack_text_search_key_noteid(&c, &key->note_id))
    532 		return 0;
    533 
    534 	if (!ndb_unpack_text_search_key_string(&c, &key->str, &key->str_len))
    535 		return 0;
    536 
    537 	return ndb_unpack_remaining_text_search_key(&c, key);
    538 }
    539 
    540 // Copies only lowercase characters to the destination string and fills the rest with null bytes.
    541 // `dst` and `src` are pointers to the destination and source strings, respectively.
    542 // `n` is the maximum number of characters to copy.
    543 static void lowercase_strncpy(char *dst, const char *src, int n) {
    544 	int j = 0, i = 0;
    545 
    546 	if (!dst || !src || n == 0) {
    547 		return;
    548 	}
    549 
    550 	while (src[i] != '\0' && j < n) {
    551 		dst[j++] = tolower(src[i++]);
    552 	}
    553 
    554 	// Null-terminate and fill the destination string
    555 	while (j < n) {
    556 		dst[j++] = '\0';
    557 	}
    558 }
    559 
    560 static inline int ndb_filter_elem_is_ptr(struct ndb_filter_field *field) {
    561 	return field->elem_type == NDB_ELEMENT_STRING || field->elem_type == NDB_ELEMENT_ID;
    562 }
    563 
    564 // Copy the filter
    565 int ndb_filter_clone(struct ndb_filter *dst, struct ndb_filter *src)
    566 {
    567 	size_t src_size, elem_size, data_size;
    568 
    569 	memcpy(dst, src, sizeof(*src));
    570 
    571 	elem_size = src->elem_buf.end - src->elem_buf.start;
    572 	data_size = src->data_buf.end - src->data_buf.start;
    573 	src_size = data_size + elem_size;
    574 
    575 	// let's only allow finalized filters to be cloned
    576 	if (!src || !src->finalized)
    577 		return 0;
    578 
    579 	dst->elem_buf.start = malloc(src_size);
    580 	dst->elem_buf.end = dst->elem_buf.start + elem_size;
    581 	dst->elem_buf.p = dst->elem_buf.end;
    582 
    583 	dst->data_buf.start = dst->elem_buf.start + elem_size;
    584 	dst->data_buf.end = dst->data_buf.start + data_size;
    585 	dst->data_buf.p = dst->data_buf.end;
    586 
    587 	if (dst->elem_buf.start == NULL)
    588 		return 0;
    589 
    590 	memcpy(dst->elem_buf.start, src->elem_buf.start, src_size);
    591 
    592 	return 1;
    593 }
    594 
    595 // "Finalize" the filter. This resizes the allocated heap buffers so that they
    596 // are as small as possible. This also prevents new fields from being added
    597 int ndb_filter_end(struct ndb_filter *filter)
    598 {
    599 #ifdef NDB_LOG
    600 	size_t orig_size;
    601 #endif
    602 	size_t data_len, elem_len;
    603 	unsigned char *rel;
    604 
    605 	assert(filter);
    606 	assert(filter->elem_buf.start);
    607 
    608 	if (filter->finalized == 1)
    609 		return 0;
    610 
    611 	// move the data buffer to the end of the element buffer and update
    612 	// all of the element pointers accordingly
    613 	data_len = filter->data_buf.p - filter->data_buf.start;
    614 	elem_len = filter->elem_buf.p - filter->elem_buf.start;
    615 #ifdef NDB_LOG
    616 	orig_size = filter->data_buf.end - filter->elem_buf.start;
    617 #endif
    618 
    619 	// cap the elem buff
    620 	filter->elem_buf.end = filter->elem_buf.p;
    621 
    622 	// move the data buffer to the end of the element buffer
    623 	memmove(filter->elem_buf.p, filter->data_buf.start, data_len);
    624 
    625 	// realloc the whole thing
    626 	rel = realloc(filter->elem_buf.start, elem_len + data_len);
    627 	if (rel) 
    628 		filter->elem_buf.start = rel;
    629 	assert(filter->elem_buf.start);
    630 	filter->elem_buf.end = filter->elem_buf.start + elem_len;
    631 	filter->elem_buf.p = filter->elem_buf.end;
    632 
    633 	filter->data_buf.start = filter->elem_buf.end;
    634 	filter->data_buf.end = filter->data_buf.start + data_len;
    635 	filter->data_buf.p = filter->data_buf.end;
    636 
    637 	filter->finalized = 1;
    638 
    639 	ndb_debug("ndb_filter_end: %ld -> %ld\n", orig_size, elem_len + data_len);
    640 
    641 	return 1;
    642 }
    643 
    644 static inline struct ndb_filter_elements *
    645 ndb_filter_get_elements_by_offset(const struct ndb_filter *filter, int offset)
    646 {
    647 	struct ndb_filter_elements *els;
    648 
    649 	if (offset < 0)
    650 		return NULL;
    651 
    652 	els = (struct ndb_filter_elements *)(filter->elem_buf.start + offset);
    653 
    654 	if ((unsigned char *)els > filter->elem_buf.p)
    655 		return NULL;
    656 
    657 	return els;
    658 }
    659 
    660 struct ndb_filter_elements *
    661 ndb_filter_current_element(const struct ndb_filter *filter)
    662 {
    663 	return ndb_filter_get_elements_by_offset(filter, filter->current);
    664 }
    665 
    666 struct ndb_filter_elements *
    667 ndb_filter_get_elements(const struct ndb_filter *filter, int index)
    668 {
    669 	if (filter->num_elements <= 0)
    670 		return NULL;
    671 
    672 	if (index > filter->num_elements-1)
    673 		return NULL;
    674 
    675 	return ndb_filter_get_elements_by_offset(filter, filter->elements[index]);
    676 }
    677 
    678 static inline unsigned char *
    679 ndb_filter_elements_data(const struct ndb_filter *filter, int offset)
    680 {
    681 	unsigned char *data;
    682 
    683 	if (offset < 0)
    684 		return NULL;
    685 
    686 	data = filter->data_buf.start + offset;
    687 	if (data > filter->data_buf.p)
    688 		return NULL;
    689 
    690 	return data;
    691 }
    692 
    693 struct ndb_filter_custom *
    694 ndb_filter_get_custom_element(const struct ndb_filter *filter, const struct ndb_filter_elements *els)
    695 {
    696 	return (struct ndb_filter_custom *)ndb_filter_elements_data(filter, els->elements[0]);
    697 }
    698 
    699 unsigned char *
    700 ndb_filter_get_id_element(const struct ndb_filter *filter, const struct ndb_filter_elements *els, int index)
    701 {
    702 	return ndb_filter_elements_data(filter, els->elements[index]);
    703 }
    704 
    705 const char *
    706 ndb_filter_get_string_element(const struct ndb_filter *filter, const struct ndb_filter_elements *els, int index)
    707 {
    708 	return (const char *)ndb_filter_elements_data(filter, els->elements[index]);
    709 }
    710 
    711 uint64_t *
    712 ndb_filter_get_int_element_ptr(struct ndb_filter_elements *els, int index)
    713 {
    714 	return &els->elements[index];
    715 }
    716 
    717 uint64_t
    718 ndb_filter_get_int_element(const struct ndb_filter_elements *els, int index)
    719 {
    720 	return els->elements[index];
    721 }
    722 
    723 int ndb_filter_init_with(struct ndb_filter *filter, int pages)
    724 {
    725 	struct cursor cur;
    726 	int page_size, elem_size, data_size, buf_size;
    727 
    728 	page_size = 4096; // assuming this, not a big deal if we're wrong
    729 
    730 	buf_size = page_size * pages;
    731 	elem_size = buf_size / 4;
    732 	data_size = buf_size - elem_size;
    733 
    734 	unsigned char *buf = malloc(buf_size);
    735 	if (!buf)
    736 		return 0;
    737 
    738 	// init memory arena for the cursor
    739 	make_cursor(buf, buf + buf_size, &cur);
    740 
    741 	cursor_slice(&cur, &filter->elem_buf, elem_size);
    742 	cursor_slice(&cur, &filter->data_buf, data_size);
    743 
    744 	// make sure we are fully allocated
    745 	assert(cur.p == cur.end);
    746 
    747 	// make sure elem_buf is the start of the buffer
    748 	assert(filter->elem_buf.start == cur.start);
    749 
    750 	filter->num_elements = 0;
    751 	filter->elements[0] = 0;
    752 	filter->current = -1;
    753 	filter->finalized = 0;
    754 
    755 	return 1;
    756 }
    757 
    758 int ndb_filter_init(struct ndb_filter *filter) {
    759 	return ndb_filter_init_with(filter, NDB_FILTER_PAGES);
    760 }
    761 
    762 void ndb_filter_destroy(struct ndb_filter *filter)
    763 {
    764 	if (filter->elem_buf.start)
    765 		free(filter->elem_buf.start);
    766 
    767 	memset(filter, 0, sizeof(*filter));
    768 }
    769 
    770 static const char *ndb_filter_field_name(enum ndb_filter_fieldtype field)
    771 {
    772 	switch (field) {
    773 	case NDB_FILTER_IDS: return "ids";
    774 	case NDB_FILTER_AUTHORS: return "authors";
    775 	case NDB_FILTER_KINDS: return "kinds";
    776 	case NDB_FILTER_TAGS: return "tags";
    777 	case NDB_FILTER_SINCE: return "since";
    778 	case NDB_FILTER_UNTIL: return "until";
    779 	case NDB_FILTER_LIMIT: return "limit";
    780 	case NDB_FILTER_SEARCH: return "search";
    781 	case NDB_FILTER_RELAYS: return "relays";
    782 	case NDB_FILTER_CUSTOM: return "custom";
    783 	}
    784 
    785 	return "unknown";
    786 }
    787 
    788 static int ndb_filter_start_field_impl(struct ndb_filter *filter, enum ndb_filter_fieldtype field, char tag)
    789 {
    790 	int i;
    791 	struct ndb_filter_elements *els, *el;
    792 
    793 	if (ndb_filter_current_element(filter)) {
    794 		ndb_debug("ndb_filter_start_field: filter field already in progress, did you forget to call ndb_filter_end_field?\n");
    795 		return 0;
    796 	}
    797 
    798 	// you can only start and end fields once
    799 	for (i = 0; i < filter->num_elements; i++) {
    800 		el = ndb_filter_get_elements(filter, i);
    801 		assert(el);
    802 		// TODO: fix this tags check to try to find matching tags
    803 		if (el->field.type == field && field != NDB_FILTER_TAGS) {
    804 			fprintf(stderr, "ndb_filter_start_field: field '%s' already exists\n",
    805 					ndb_filter_field_name(field));
    806 			return 0;
    807 		}
    808 	}
    809 
    810 	filter->current = filter->elem_buf.p - filter->elem_buf.start;
    811 	els = ndb_filter_current_element(filter);
    812 	assert(els);
    813 
    814 	// advance elem buffer to the variable data section
    815 	if (!cursor_skip(&filter->elem_buf, sizeof(struct ndb_filter_elements))) {
    816 		fprintf(stderr, "ndb_filter_start_field: '%s' oom (todo: realloc?)\n",
    817 				ndb_filter_field_name(field));
    818 		return 0;
    819 	}
    820 
    821 	els->field.type = field;
    822 	els->field.tag = tag;
    823 	els->field.elem_type = 0;
    824 	els->count = 0;
    825 
    826 	return 1;
    827 }
    828 
    829 int ndb_filter_start_field(struct ndb_filter *filter, enum ndb_filter_fieldtype field)
    830 {
    831 	return ndb_filter_start_field_impl(filter, field, 0);
    832 }
    833 
    834 int ndb_filter_start_tag_field(struct ndb_filter *filter, char tag)
    835 {
    836 	return ndb_filter_start_field_impl(filter, NDB_FILTER_TAGS, tag);
    837 }
    838 
    839 static int ndb_filter_add_element(struct ndb_filter *filter, union ndb_filter_element el)
    840 {
    841 	struct ndb_filter_elements *current;
    842 	uint64_t offset;
    843 
    844 	if (!(current = ndb_filter_current_element(filter)))
    845 		return 0;
    846 
    847 	offset = filter->data_buf.p - filter->data_buf.start;
    848 
    849 	switch (current->field.type) {
    850 	case NDB_FILTER_CUSTOM:
    851 		if (!cursor_push(&filter->data_buf, (unsigned char *)&el, sizeof(el)))
    852 			return 0;
    853 		break;
    854 	case NDB_FILTER_IDS:
    855 	case NDB_FILTER_AUTHORS:
    856 		if (!cursor_push(&filter->data_buf, (unsigned char *)el.id, 32))
    857 			return 0;
    858 		break;
    859 	case NDB_FILTER_KINDS:
    860 		offset = el.integer;
    861 		break;
    862 	case NDB_FILTER_SINCE:
    863 	case NDB_FILTER_UNTIL:
    864 	case NDB_FILTER_LIMIT:
    865 		// only one allowed for since/until
    866 		if (current->count != 0)
    867 			return 0;
    868 		offset = el.integer;
    869 		break;
    870 	case NDB_FILTER_SEARCH:
    871 		if (current->field.elem_type != NDB_ELEMENT_STRING) {
    872 			return 0;
    873 		}
    874 		if (!cursor_push(&filter->data_buf, (unsigned char *)el.string.string, el.string.len))
    875 			return 0;
    876 		if (!cursor_push_byte(&filter->data_buf, 0))
    877 			return 0;
    878 		break;
    879 	case NDB_FILTER_TAGS:
    880 		switch (current->field.elem_type) {
    881 		case NDB_ELEMENT_ID:
    882 			if (!cursor_push(&filter->data_buf, (unsigned char *)el.id, 32))
    883 				return 0;
    884 			break;
    885 		case NDB_ELEMENT_STRING:
    886 			if (!cursor_push(&filter->data_buf, (unsigned char *)el.string.string, el.string.len))
    887 				return 0;
    888 			if (!cursor_push_byte(&filter->data_buf, 0))
    889 				return 0;
    890 			break;
    891 		case NDB_ELEMENT_INT:
    892 			// ints are not allowed in tag filters
    893 		case NDB_ELEMENT_UNKNOWN:
    894 		case NDB_ELEMENT_CUSTOM:
    895 			return 0;
    896 		}
    897 		// push a pointer of the string in the databuf as an element
    898 		break;
    899 	case NDB_FILTER_RELAYS:
    900 		if (current->field.elem_type != NDB_ELEMENT_STRING) {
    901 			return 0;
    902 		}
    903 		if (!cursor_push(&filter->data_buf, (unsigned char *)el.string.string, el.string.len))
    904 			return 0;
    905 		if (!cursor_push_byte(&filter->data_buf, 0))
    906 			return 0;
    907 		break;
    908 	}
    909 
    910 	if (!cursor_push(&filter->elem_buf, (unsigned char *)&offset,
    911 			 sizeof(offset))) {
    912 		return 0;
    913 	}
    914 
    915 	current->count++;
    916 
    917 	return 1;
    918 }
    919 
    920 static int ndb_filter_set_elem_type(struct ndb_filter *filter,
    921 				    enum ndb_generic_element_type elem_type)
    922 {
    923 	enum ndb_generic_element_type current_elem_type;
    924 	struct ndb_filter_elements *current;
    925 
    926 	if (!(current = ndb_filter_current_element(filter)))
    927 		return 0;
    928 
    929 	current_elem_type = current->field.elem_type;
    930 
    931 	// element types must be uniform
    932 	if (current_elem_type != elem_type && current_elem_type != NDB_ELEMENT_UNKNOWN) {
    933 		fprintf(stderr, "ndb_filter_set_elem_type: element types must be uniform\n");
    934 		return 0;
    935 	}
    936 
    937 	current->field.elem_type = elem_type;
    938 
    939 	return 1;
    940 }
    941 
    942 
    943 int ndb_filter_add_str_element_len(struct ndb_filter *filter, const char *str, int len)
    944 {
    945 	union ndb_filter_element el;
    946 	struct ndb_filter_elements *current;
    947 
    948 	if (!(current = ndb_filter_current_element(filter)))
    949 		return 0;
    950 
    951 	// only generic tags and search queries are allowed to have strings
    952 	switch (current->field.type) {
    953 	case NDB_FILTER_SINCE:
    954 	case NDB_FILTER_UNTIL:
    955 	case NDB_FILTER_LIMIT:
    956 	case NDB_FILTER_IDS:
    957 	case NDB_FILTER_AUTHORS:
    958 	case NDB_FILTER_KINDS:
    959 	case NDB_FILTER_CUSTOM:
    960 		return 0;
    961 	case NDB_FILTER_SEARCH:
    962 		if (current->count == 1) {
    963 			// you can't add more than one string to a search
    964 			return 0;
    965 		}
    966 		break;
    967 	case NDB_FILTER_RELAYS:
    968 	case NDB_FILTER_TAGS:
    969 		break;
    970 	}
    971 
    972 	if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_STRING))
    973 		return 0;
    974 
    975 	el.string.string = str;
    976 	el.string.len = len;
    977 
    978 	return ndb_filter_add_element(filter, el);
    979 }
    980 
    981 int ndb_filter_add_str_element(struct ndb_filter *filter, const char *str)
    982 {
    983 	return ndb_filter_add_str_element_len(filter, str, strlen(str));
    984 }
    985 
    986 int ndb_filter_add_custom_filter_element(struct ndb_filter *filter, ndb_filter_callback_fn *cb, void *ctx)
    987 {
    988 	union ndb_filter_element el;
    989 	struct ndb_filter_elements *current;
    990 	struct ndb_filter_custom custom;
    991 
    992 	custom.cb = cb;
    993 	custom.ctx = ctx;
    994 
    995 	if (!(current = ndb_filter_current_element(filter)))
    996 		return 0;
    997 
    998 	switch (current->field.type) {
    999 	case NDB_FILTER_CUSTOM:
   1000 		break;
   1001 	case NDB_FILTER_IDS:
   1002 	case NDB_FILTER_AUTHORS:
   1003 	case NDB_FILTER_TAGS:
   1004 	case NDB_FILTER_SEARCH:
   1005 	case NDB_FILTER_RELAYS:
   1006 	case NDB_FILTER_KINDS:
   1007 	case NDB_FILTER_SINCE:
   1008 	case NDB_FILTER_UNTIL:
   1009 	case NDB_FILTER_LIMIT:
   1010 		return 0;
   1011 	}
   1012 
   1013 	if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_CUSTOM))
   1014 		return 0;
   1015 
   1016 	el.custom_filter = custom;
   1017 
   1018 	return ndb_filter_add_element(filter, el);
   1019 }
   1020 
   1021 int ndb_filter_add_int_element(struct ndb_filter *filter, uint64_t integer)
   1022 {
   1023 	union ndb_filter_element el;
   1024 	struct ndb_filter_elements *current;
   1025 	if (!(current = ndb_filter_current_element(filter)))
   1026 		return 0;
   1027 
   1028 	switch (current->field.type) {
   1029 	case NDB_FILTER_IDS:
   1030 	case NDB_FILTER_AUTHORS:
   1031 	case NDB_FILTER_TAGS:
   1032 	case NDB_FILTER_SEARCH:
   1033 	case NDB_FILTER_RELAYS:
   1034 	case NDB_FILTER_CUSTOM:
   1035 		return 0;
   1036 	case NDB_FILTER_KINDS:
   1037 	case NDB_FILTER_SINCE:
   1038 	case NDB_FILTER_UNTIL:
   1039 	case NDB_FILTER_LIMIT:
   1040 		break;
   1041 	}
   1042 
   1043 	if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_INT))
   1044 		return 0;
   1045 
   1046 	el.integer = integer;
   1047 
   1048 	return ndb_filter_add_element(filter, el);
   1049 }
   1050 
   1051 int ndb_filter_add_id_element(struct ndb_filter *filter, const unsigned char *id)
   1052 {
   1053 	union ndb_filter_element el;
   1054 	struct ndb_filter_elements *current;
   1055 
   1056 	if (!(current = ndb_filter_current_element(filter)))
   1057 		return 0;
   1058 
   1059 	// only certain filter types allow pushing id elements
   1060 	switch (current->field.type) {
   1061 	case NDB_FILTER_SINCE:
   1062 	case NDB_FILTER_UNTIL:
   1063 	case NDB_FILTER_LIMIT:
   1064 	case NDB_FILTER_KINDS:
   1065 	case NDB_FILTER_SEARCH:
   1066 	case NDB_FILTER_RELAYS:
   1067 	case NDB_FILTER_CUSTOM:
   1068 		return 0;
   1069 	case NDB_FILTER_IDS:
   1070 	case NDB_FILTER_AUTHORS:
   1071 	case NDB_FILTER_TAGS:
   1072 		break;
   1073 	}
   1074 
   1075 	if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_ID))
   1076 		return 0;
   1077 
   1078 	// this is needed so that generic filters know its an id
   1079 	el.id = id;
   1080 
   1081 	return ndb_filter_add_element(filter, el);
   1082 }
   1083 
   1084 static int ndb_tag_filter_matches(struct ndb_filter *filter,
   1085 				  struct ndb_filter_elements *els,
   1086 				  struct ndb_note *note)
   1087 {
   1088 	int i;
   1089 	const unsigned char *id;
   1090 	const char *el_str;
   1091 	struct ndb_iterator iter, *it = &iter;
   1092 	struct ndb_str str;
   1093 
   1094 	ndb_tags_iterate_start(note, it);
   1095 
   1096 	while (ndb_tags_iterate_next(it)) {
   1097 		// we're looking for tags with 2 or more entries: ["p", id], etc
   1098 		if (it->tag->count < 2)
   1099 			continue;
   1100 
   1101 		str = ndb_tag_str(note, it->tag, 0);
   1102 
   1103 		// we only care about packed strings (single char, etc)
   1104 		if (str.flag != NDB_PACKED_STR)
   1105 			continue;
   1106 
   1107 		// do we have #e matching e (or p, etc)
   1108 		if (str.str[0] != els->field.tag || str.str[1] != 0)
   1109 			continue;
   1110 
   1111 		str = ndb_tag_str(note, it->tag, 1);
   1112 
   1113 		switch (els->field.elem_type) {
   1114 		case NDB_ELEMENT_ID:
   1115 			// if our filter element type is an id, then we
   1116 			// expect a packed id in the tag, otherwise skip
   1117 			if (str.flag != NDB_PACKED_ID)
   1118 				continue;
   1119 			break;
   1120 		case NDB_ELEMENT_STRING:
   1121 			// if our filter element type is a string, then
   1122 			// we should not expect an id
   1123 			if (str.flag == NDB_PACKED_ID)
   1124 				continue;
   1125 
   1126 			break;
   1127 		case NDB_ELEMENT_UNKNOWN:
   1128 		default:
   1129 			// For some reason the element type is not set. It's
   1130 			// possible nothing was added to the generic filter?
   1131 			// Let's just fail here and log a note for debugging
   1132 			fprintf(stderr, "UNUSUAL ndb_tag_filter_matches: have unknown element type %d\n", els->field.elem_type);
   1133 			return 0;
   1134 		}
   1135 
   1136 		for (i = 0; i < els->count; i++) {
   1137 			switch (els->field.elem_type) {
   1138 			case NDB_ELEMENT_ID:
   1139 				id = ndb_filter_get_id_element(filter, els, i);
   1140 				if (!memcmp(id, str.id, 32))
   1141 					return 1;
   1142 				break;
   1143 			case NDB_ELEMENT_STRING:
   1144 				el_str = ndb_filter_get_string_element(filter, els, i);
   1145 				if (!strcmp(el_str, str.str))
   1146 					return 1;
   1147 				break;
   1148 			case NDB_ELEMENT_INT:
   1149 				// int elements int tag queries are not supported
   1150 			case NDB_ELEMENT_UNKNOWN:
   1151 			case NDB_ELEMENT_CUSTOM:
   1152 				return 0;
   1153 			}
   1154 		}
   1155 	}
   1156 
   1157 	return 0;
   1158 }
   1159 
   1160 struct search_id_state {
   1161 	struct ndb_filter *filter;
   1162 	struct ndb_filter_elements *els;
   1163 	unsigned char *key;
   1164 };
   1165 
   1166 static int compare_ids(const void *pa, const void *pb)
   1167 {
   1168         unsigned char *a = *(unsigned char **)pa;
   1169         unsigned char *b = *(unsigned char **)pb;
   1170 
   1171         return memcmp(a, b, 32);
   1172 }
   1173 
   1174 static int compare_strs(const void *pa, const void *pb)
   1175 {
   1176         const char *a = *(const char **)pa;
   1177         const char *b = *(const char **)pb;
   1178 
   1179         return strcmp(a, b);
   1180 }
   1181 
   1182 static int search_strs(const void *ctx, const void *mstr_ptr)
   1183 {
   1184 	// we reuse search_id_state here and just cast to (const char *) when
   1185 	// needed
   1186 	struct search_id_state *state;
   1187 	const char *mstr_str;
   1188 	uint32_t mstr;
   1189 
   1190 	state = (struct search_id_state *)ctx;
   1191 	mstr = *(uint32_t *)mstr_ptr;
   1192 
   1193 	mstr_str = (const char *)ndb_filter_elements_data(state->filter, mstr);
   1194 	assert(mstr_str);
   1195 
   1196 	return strcmp((const char *)state->key, mstr_str);
   1197 }
   1198 
   1199 static int search_ids(const void *ctx, const void *mid_ptr)
   1200 {
   1201 	struct search_id_state *state;
   1202 	unsigned char *mid_id;
   1203 	uint32_t mid;
   1204 
   1205 	state = (struct search_id_state *)ctx;
   1206 	mid = *(uint32_t *)mid_ptr;
   1207 
   1208 	mid_id = ndb_filter_elements_data(state->filter, mid);
   1209 	assert(mid_id);
   1210 
   1211 	return memcmp(state->key, mid_id, 32);
   1212 }
   1213 
   1214 static int compare_kinds(const void *pa, const void *pb)
   1215 {
   1216 
   1217 	// NOTE: this should match type in `union ndb_filter_element`
   1218 	uint64_t a = *(uint64_t *)pa;
   1219 	uint64_t b = *(uint64_t *)pb;
   1220 
   1221 	if (a < b) {
   1222 		return -1;
   1223 	} else if (a > b) {
   1224 		return 1;
   1225 	} else {
   1226 		return 0;
   1227 	}
   1228 }
   1229 
   1230 //
   1231 // returns 1 if a filter matches a note
   1232 static int ndb_filter_matches_with(struct ndb_filter *filter,
   1233 				   struct ndb_note *note, int already_matched,
   1234 				   struct ndb_note_relay_iterator *relay_iter)
   1235 {
   1236 	int i, j;
   1237 	struct ndb_filter_elements *els;
   1238 	struct search_id_state state;
   1239 	struct ndb_filter_custom *custom;
   1240 
   1241 	state.filter = filter;
   1242 
   1243 	for (i = 0; i < filter->num_elements; i++) {
   1244 		els = ndb_filter_get_elements(filter, i);
   1245 		state.els = els;
   1246 		assert(els);
   1247 
   1248 		// if we know we already match from a query scan result,
   1249 		// we can skip this check
   1250 		if ((1 << els->field.type) & already_matched)
   1251 			continue;
   1252 
   1253 		switch (els->field.type) {
   1254                 case NDB_FILTER_KINDS:
   1255                         for (j = 0; j < els->count; j++) {
   1256                                 if ((unsigned int)els->elements[j] == note->kind)
   1257                                         goto cont;
   1258 			}
   1259 			break;
   1260 		case NDB_FILTER_RELAYS:
   1261 			// for each relay the note was seen on, see if any match 
   1262 			if (!relay_iter) {
   1263 				assert(!"expected relay iterator...");
   1264 				break;
   1265 			}
   1266 			while ((state.key = (unsigned char *)ndb_note_relay_iterate_next(relay_iter))) {
   1267 				// relays in filters are always sorted
   1268 				if (bsearch(&state, &els->elements[0], els->count,
   1269 					    sizeof(els->elements[0]), search_strs)) {
   1270 					ndb_note_relay_iterate_close(relay_iter);
   1271 					goto cont;
   1272 				}
   1273 			}
   1274 			ndb_note_relay_iterate_close(relay_iter);
   1275 			break;
   1276 		case NDB_FILTER_IDS:
   1277 			state.key = ndb_note_id(note);
   1278 			if (bsearch(&state, &els->elements[0], els->count,
   1279 				    sizeof(els->elements[0]), search_ids)) {
   1280 				continue;
   1281 			}
   1282 			break;
   1283 		case NDB_FILTER_AUTHORS:
   1284 			state.key = ndb_note_pubkey(note);
   1285 			if (bsearch(&state, &els->elements[0], els->count,
   1286 				    sizeof(els->elements[0]), search_ids)) {
   1287 				continue;
   1288 			}
   1289 			break;
   1290 		case NDB_FILTER_TAGS:
   1291 			if (ndb_tag_filter_matches(filter, els, note))
   1292 				continue;
   1293 			break;
   1294 		case NDB_FILTER_SINCE:
   1295 			assert(els->count == 1);
   1296 			if (note->created_at >= els->elements[0])
   1297 				continue;
   1298 			break;
   1299 		case NDB_FILTER_UNTIL:
   1300 			assert(els->count == 1);
   1301 			if (note->created_at < els->elements[0])
   1302 				continue;
   1303 			break;
   1304 		case NDB_FILTER_SEARCH:
   1305 			// TODO: matching search filters will need an accelerated
   1306 			// data structure, like our minimal perfect hashmap
   1307 			// idea for mutewords.
   1308 			//
   1309 			// We'll also want to store tokenized words in the filter
   1310 			// itself, so that we can walk over each word and check
   1311 			// the hashmap to see if the note contains at least
   1312 			// one word.
   1313 			//
   1314 			// For now we always return true, since we assume
   1315 			// the search index will be walked for these kinds
   1316 			// of queries.
   1317 			continue;
   1318 		case NDB_FILTER_CUSTOM:
   1319 			custom = ndb_filter_get_custom_element(filter, els);
   1320 			if (custom->cb(custom->ctx, note))
   1321 				continue;
   1322 			break;
   1323 
   1324 		case NDB_FILTER_LIMIT:
   1325 cont:
   1326 			continue;
   1327 		}
   1328 
   1329 		// all need to match
   1330 		return 0;
   1331 	}
   1332 
   1333 	return 1;
   1334 }
   1335 
   1336 int ndb_filter_matches(struct ndb_filter *filter, struct ndb_note *note)
   1337 {
   1338 	return ndb_filter_matches_with(filter, note, 0, NULL);
   1339 }
   1340 
   1341 int ndb_filter_matches_with_relay(struct ndb_filter *filter,
   1342 				  struct ndb_note *note,
   1343 				  struct ndb_note_relay_iterator *note_relay_iter)
   1344 {
   1345 	return ndb_filter_matches_with(filter, note, 0, note_relay_iter);
   1346 }
   1347 
   1348 // because elements are stored as offsets and qsort doesn't support context,
   1349 // we do a dumb thing where we convert elements to pointers and back if we
   1350 // are doing an id or string sort
   1351 static void sort_filter_elements(struct ndb_filter *filter,
   1352 				 struct ndb_filter_elements *els,
   1353 				 int (*cmp)(const void *, const void *))
   1354 {
   1355 	int i;
   1356 
   1357 	assert(ndb_filter_elem_is_ptr(&els->field));
   1358 
   1359 	for (i = 0; i < els->count; i++)
   1360 		els->elements[i] += (uint64_t)filter->data_buf.start;
   1361 
   1362 	qsort(&els->elements[0], els->count, sizeof(els->elements[0]), cmp);
   1363 
   1364 	for (i = 0; i < els->count; i++)
   1365 		els->elements[i] -= (uint64_t)filter->data_buf.start;
   1366 }
   1367 
   1368 static int ndb_filter_field_eq(struct ndb_filter *a_filt,
   1369 			       struct ndb_filter_elements *a_field,
   1370 			       struct ndb_filter *b_filt,
   1371 			       struct ndb_filter_elements *b_field)
   1372 {
   1373 	int i;
   1374 	const char *a_str, *b_str;
   1375 	unsigned char *a_id, *b_id;
   1376 	uint64_t a_int, b_int;
   1377 	struct ndb_filter_custom *a_custom, *b_custom;
   1378 
   1379 	if (a_field->count != b_field->count)
   1380 		return 0;
   1381 
   1382 	if (a_field->field.type != b_field->field.type) {
   1383 		ndb_debug("UNUSUAL: field types do not match in ndb_filter_field_eq\n");
   1384 		return 0;
   1385 	}
   1386 
   1387 	if (a_field->field.elem_type != b_field->field.elem_type) {
   1388 		ndb_debug("UNUSUAL: field element types do not match in ndb_filter_field_eq\n");
   1389 		return 0;
   1390 	}
   1391 
   1392 	if (a_field->field.elem_type == NDB_ELEMENT_UNKNOWN) {
   1393 		ndb_debug("UNUSUAL: field element types are unknown\n");
   1394 		return 0;
   1395 	}
   1396 
   1397 	for (i = 0; i < a_field->count; i++) {
   1398 		switch (a_field->field.elem_type) {
   1399 		case NDB_ELEMENT_CUSTOM:
   1400 			a_custom = ndb_filter_get_custom_element(a_filt, a_field);
   1401 			b_custom = ndb_filter_get_custom_element(b_filt, b_field);
   1402 			if (memcmp(a_custom, b_custom, sizeof(*a_custom)))
   1403 				return 0;
   1404 			break;
   1405 		case NDB_ELEMENT_UNKNOWN:
   1406 			return 0;
   1407 		case NDB_ELEMENT_STRING:
   1408 			a_str = ndb_filter_get_string_element(a_filt, a_field, i);
   1409 			b_str = ndb_filter_get_string_element(b_filt, b_field, i);
   1410 			if (strcmp(a_str, b_str))
   1411 				return 0;
   1412 			break;
   1413 		case NDB_ELEMENT_ID:
   1414 			a_id = ndb_filter_get_id_element(a_filt, a_field, i);
   1415 			b_id = ndb_filter_get_id_element(b_filt, b_field, i);
   1416 			if (memcmp(a_id, b_id, 32))
   1417 				return 0;
   1418 			break;
   1419 		case NDB_ELEMENT_INT:
   1420 			a_int = ndb_filter_get_int_element(a_field, i);
   1421 			b_int = ndb_filter_get_int_element(b_field, i);
   1422 			if (a_int != b_int)
   1423 				return 0;
   1424 			break;
   1425 		}
   1426 	}
   1427 
   1428 	return 1;
   1429 }
   1430 
   1431 void ndb_filter_end_field(struct ndb_filter *filter)
   1432 {
   1433 	int cur_offset;
   1434 	struct ndb_filter_elements *cur;
   1435 
   1436 	cur_offset = filter->current;
   1437 
   1438 	if (!(cur = ndb_filter_current_element(filter)))
   1439 		return;
   1440 
   1441 	filter->elements[filter->num_elements++] = cur_offset;
   1442 
   1443 	// sort elements for binary search
   1444 	switch (cur->field.type) {
   1445 	case NDB_FILTER_IDS:
   1446 	case NDB_FILTER_AUTHORS:
   1447 		sort_filter_elements(filter, cur, compare_ids);
   1448 		break;
   1449 	case NDB_FILTER_RELAYS:
   1450 		sort_filter_elements(filter, cur, compare_strs);
   1451 		break;
   1452 	case NDB_FILTER_KINDS:
   1453 		qsort(&cur->elements[0], cur->count,
   1454 		      sizeof(cur->elements[0]), compare_kinds);
   1455 		break;
   1456 	case NDB_FILTER_TAGS:
   1457 		// TODO: generic tag search sorting
   1458 		break;
   1459 	case NDB_FILTER_SINCE:
   1460 	case NDB_FILTER_CUSTOM:
   1461 	case NDB_FILTER_UNTIL:
   1462 	case NDB_FILTER_LIMIT:
   1463 	case NDB_FILTER_SEARCH:
   1464 		// don't need to sort these
   1465 		break;
   1466 	}
   1467 
   1468 	filter->current = -1;
   1469 
   1470 }
   1471 
   1472 static void ndb_filter_group_init(struct ndb_filter_group *group)
   1473 {
   1474 	group->num_filters = 0;
   1475 }
   1476 
   1477 static int ndb_filter_group_add(struct ndb_filter_group *group,
   1478 				struct ndb_filter *filter)
   1479 {
   1480 	if (group->num_filters + 1 > MAX_FILTERS)
   1481 		return 0;
   1482 
   1483 	return ndb_filter_clone(&group->filters[group->num_filters++], filter);
   1484 }
   1485 
   1486 static int ndb_filter_group_matches(struct ndb_filter_group *group,
   1487 				    struct ndb_note *note)
   1488 {
   1489 	int i;
   1490 	struct ndb_filter *filter;
   1491 
   1492 	if (group->num_filters == 0)
   1493 		return 1;
   1494 
   1495 	for (i = 0; i < group->num_filters; i++) {
   1496 		filter = &group->filters[i];
   1497 
   1498 		if (ndb_filter_matches(filter, note))
   1499 			return 1;
   1500 	}
   1501 
   1502 	return 0;
   1503 }
   1504 
   1505 static void ndb_make_search_key(struct ndb_search_key *key, unsigned char *id,
   1506 			        uint64_t timestamp, const char *search)
   1507 {
   1508 	memcpy(key->id, id, 32);
   1509 	key->timestamp = timestamp;
   1510 	lowercase_strncpy(key->search, search, sizeof(key->search) - 1);
   1511 	key->search[sizeof(key->search) - 1] = '\0';
   1512 }
   1513 
   1514 static int ndb_write_profile_search_index(struct ndb_txn *txn,
   1515 					  struct ndb_search_key *index_key,
   1516 					  uint64_t profile_key)
   1517 {
   1518 	int rc;
   1519 	MDB_val key, val;
   1520 
   1521 	key.mv_data = index_key;
   1522 	key.mv_size = sizeof(*index_key);
   1523 	val.mv_data = &profile_key;
   1524 	val.mv_size = sizeof(profile_key);
   1525 
   1526 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_SEARCH],
   1527 			  &key, &val, 0)))
   1528 	{
   1529 		ndb_debug("ndb_write_profile_search_index failed: %s\n",
   1530 			  mdb_strerror(rc));
   1531 		return 0;
   1532 	}
   1533 
   1534 	return 1;
   1535 }
   1536 
   1537 
   1538 // map usernames and display names to profile keys for user searching
   1539 static int ndb_write_profile_search_indices(struct ndb_txn *txn,
   1540 					    struct ndb_note *note,
   1541 					    uint64_t profile_key,
   1542 					    void *profile_root)
   1543 {
   1544 	struct ndb_search_key index;
   1545 	NdbProfileRecord_table_t profile_record;
   1546 	NdbProfile_table_t profile;
   1547 
   1548 	profile_record = NdbProfileRecord_as_root(profile_root);
   1549 	profile = NdbProfileRecord_profile_get(profile_record);
   1550 
   1551 	const char *name = NdbProfile_name_get(profile);
   1552 	const char *display_name = NdbProfile_display_name_get(profile);
   1553 
   1554 	// words + pubkey + created
   1555 	if (name) {
   1556 		ndb_make_search_key(&index, note->pubkey, note->created_at,
   1557 				    name);
   1558 		if (!ndb_write_profile_search_index(txn, &index, profile_key))
   1559 			return 0;
   1560 	}
   1561 
   1562 	if (display_name) {
   1563 		// don't write the same name/display_name twice
   1564 		if (name && !strcmp(display_name, name)) {
   1565 			return 1;
   1566 		}
   1567 		ndb_make_search_key(&index, note->pubkey, note->created_at,
   1568 				    display_name);
   1569 		if (!ndb_write_profile_search_index(txn, &index, profile_key))
   1570 			return 0;
   1571 	}
   1572 
   1573 	return 1;
   1574 }
   1575 
   1576 static inline void ndb_tsid_init(struct ndb_tsid *key, unsigned char *id,
   1577 				 uint64_t timestamp)
   1578 {
   1579 	memcpy(key->id, id, 32);
   1580 	key->timestamp = timestamp;
   1581 }
   1582 
   1583 static inline void ndb_tsid_low(struct ndb_tsid *key, unsigned char *id)
   1584 {
   1585 	memcpy(key->id, id, 32);
   1586 	key->timestamp = 0;
   1587 }
   1588 
   1589 static inline void ndb_u64_ts_init(struct ndb_u64_ts *key, uint64_t integer,
   1590 				     uint64_t timestamp)
   1591 {
   1592 	key->u64 = integer;
   1593 	key->timestamp = timestamp;
   1594 }
   1595 
   1596 // useful for range-searching for the latest key with a clustered created_at timen
   1597 static inline void ndb_tsid_high(struct ndb_tsid *key, const unsigned char *id)
   1598 {
   1599 	memcpy(key->id, id, 32);
   1600 	key->timestamp = UINT64_MAX;
   1601 }
   1602 
   1603 static int _ndb_begin_query(struct ndb *ndb, struct ndb_txn *txn, int flags)
   1604 {
   1605 	txn->lmdb = &ndb->lmdb;
   1606 	MDB_txn **mdb_txn = (MDB_txn **)&txn->mdb_txn;
   1607 	if (!txn->lmdb->env)
   1608 		return 0;
   1609 	return mdb_txn_begin(txn->lmdb->env, NULL, flags, mdb_txn) == 0;
   1610 }
   1611 
   1612 int ndb_begin_query(struct ndb *ndb, struct ndb_txn *txn)
   1613 {
   1614 	return _ndb_begin_query(ndb, txn, MDB_RDONLY);
   1615 }
   1616 
   1617 // this should only be used in migrations, etc
   1618 static int ndb_begin_rw_query(struct ndb *ndb, struct ndb_txn *txn)
   1619 {
   1620 	return _ndb_begin_query(ndb, txn, 0);
   1621 }
   1622 
   1623 static int ndb_db_is_index(enum ndb_dbs index)
   1624 {
   1625 	switch (index) {
   1626 		case NDB_DB_NOTE:
   1627 		case NDB_DB_META:
   1628 		case NDB_DB_PROFILE:
   1629 		case NDB_DB_NOTE_ID:
   1630 		case NDB_DB_NDB_META:
   1631 		case NDB_DB_PROFILE_SEARCH:
   1632 		case NDB_DB_PROFILE_LAST_FETCH:
   1633 		case NDB_DB_NOTE_RELAYS:
   1634 		case NDB_DBS:
   1635 			return 0;
   1636 		case NDB_DB_PROFILE_PK:
   1637 		case NDB_DB_NOTE_KIND:
   1638 		case NDB_DB_NOTE_TEXT:
   1639 		case NDB_DB_NOTE_BLOCKS:
   1640 		case NDB_DB_NOTE_TAGS:
   1641 		case NDB_DB_NOTE_PUBKEY:
   1642 		case NDB_DB_NOTE_PUBKEY_KIND:
   1643 		case NDB_DB_NOTE_RELAY_KIND:
   1644 			return 1;
   1645 	}
   1646 
   1647 	return 0;
   1648 }
   1649 
   1650 static inline void ndb_id_u64_ts_init(struct ndb_id_u64_ts *key,
   1651 				      unsigned char *id, uint64_t iu64,
   1652 				      uint64_t timestamp)
   1653 {
   1654 	memcpy(key->id, id, 32);
   1655 	key->u64 = iu64;
   1656 	key->timestamp = timestamp;
   1657 }
   1658 
   1659 // formats the relay url buffer for the NDB_DB_NOTE_RELAYS value. It's a
   1660 // null terminated string padded to 8 bytes (we must keep the entire database
   1661 // aligned to 8 bytes at all times)
   1662 static int prepare_relay_buf(char *relay_buf, int bufsize, const char *relay,
   1663 			     int relay_len)
   1664 {
   1665 	struct cursor cur;
   1666 
   1667 	// make sure the size of the buffer is aligned
   1668 	assert((bufsize % 8) == 0);
   1669 
   1670 	make_cursor((unsigned char *)relay_buf, (unsigned char *)relay_buf + bufsize, &cur);
   1671 
   1672 	// push the relay string
   1673 	if (!cursor_push(&cur, (unsigned char *)relay, relay_len))
   1674 		return 0;
   1675 
   1676 	// relay urls are null terminated for convenience
   1677 	if (!cursor_push_byte(&cur, 0))
   1678 		return 0;
   1679 
   1680 	// align the buffer
   1681 	if (!cursor_align(&cur, 8))
   1682 		return 0;
   1683 
   1684 	return cur.p - cur.start;
   1685 }
   1686 
   1687 // Write to the note_id -> relay_url database. This records where notes
   1688 // have been seen
   1689 static int ndb_write_note_relay(struct ndb_txn *txn, uint64_t note_key,
   1690 				const char *relay, uint8_t relay_len)
   1691 {
   1692 	char relay_buf[256];
   1693 	int rc, len;
   1694 	MDB_val k, v;
   1695 
   1696 	if (relay == NULL || relay_len == 0) {
   1697 		ndb_debug("relay is NULL in ndb_write_note_relay? '%s' %d\n", relay, relay_len);
   1698 		return 0;
   1699 	}
   1700 
   1701 	ndb_debug("writing note_relay '%s' for notekey:%" PRIu64 "\n", relay, note_key);
   1702 
   1703 	if (!(len = prepare_relay_buf(relay_buf, sizeof(relay_buf), relay, relay_len))) {
   1704 		fprintf(stderr, "relay url '%s' too large when writing note relay index\n", relay);
   1705 		return 0;
   1706 	}
   1707 
   1708 	assert((len % 8) == 0);
   1709 
   1710 	k.mv_data = &note_key;
   1711 	k.mv_size = sizeof(note_key);
   1712 
   1713 	v.mv_data = relay_buf;
   1714 	v.mv_size = len;
   1715 
   1716 	// NODUPDATA is specified so that we don't accidently add duplicate
   1717 	// key/value pairs
   1718 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAYS],
   1719 			  &k, &v, MDB_NODUPDATA)))
   1720 	{
   1721 		ndb_debug("ndb_write_note_relay failed: %s\n", mdb_strerror(rc));
   1722 		return 0;
   1723 	}
   1724 
   1725 	ndb_debug("wrote %d bytes to note relay: '%s'\n", len, relay_buf);
   1726 
   1727 	return 1;
   1728 }
   1729 
   1730 struct ndb_relay_kind_key {
   1731 	uint64_t note_key;
   1732 	uint64_t kind;
   1733 	uint64_t created_at;
   1734 	uint8_t relay_len;
   1735 	const char *relay;
   1736 };
   1737 
   1738 static int ndb_relay_kind_key_init(
   1739 		struct ndb_relay_kind_key *key,
   1740 		uint64_t note_key,
   1741 		uint64_t kind,
   1742 		uint64_t created_at,
   1743 		const char *relay)
   1744 {
   1745 	if (relay == NULL)
   1746 		return 0;
   1747 
   1748 	key->relay = relay;
   1749 	key->relay_len = strlen(relay);
   1750 	if (key->relay_len > 248)
   1751 		return 0;
   1752 
   1753 	key->note_key = note_key;
   1754 	key->kind = kind;
   1755 	key->created_at = created_at;
   1756 	return 1;
   1757 }
   1758 
   1759 
   1760 // create a range key for a relay kind query
   1761 static int ndb_relay_kind_key_init_high(
   1762 		struct ndb_relay_kind_key *key,
   1763 		const char *relay,
   1764 		uint64_t kind,
   1765 		uint64_t until)
   1766 {
   1767 	return ndb_relay_kind_key_init(key, UINT64_MAX, kind, UINT64_MAX, relay);
   1768 }
   1769 
   1770 static void ndb_parse_relay_kind_key(struct ndb_relay_kind_key *key, unsigned char *buf)
   1771 {
   1772 	// WE ARE ASSUMING WE ARE PARSING FROM AN ALIGNED BUFFER HERE
   1773 	assert((uint64_t)buf % 8 == 0);
   1774 	// - note_id:        00 + 8 bytes
   1775 	// - kind:           08 + 8 bytes
   1776 	// - created_at:     16 + 8 bytes
   1777 	// - relay_url_size: 24 + 1 byte
   1778 	// - relay_url:      25 + n byte null-terminated string
   1779 	// - pad to 8 byte alignment
   1780 	key->note_key   =  *(uint64_t*) (buf + 0);
   1781 	key->kind       =  *(uint64_t*) (buf + 8);
   1782 	key->created_at =  *(uint64_t*) (buf + 16);
   1783 	key->relay_len  =   *(uint8_t*) (buf + 24);
   1784 	key->relay      = (const char*) (buf + 25);
   1785 }
   1786 
   1787 static void ndb_debug_relay_kind_key(struct ndb_relay_kind_key *key)
   1788 {
   1789 	ndb_debug("note_key:%" PRIu64 " kind:%" PRIu64 " created_at:%" PRIu64 " '%s'\n",
   1790 			key->note_key, key->kind, key->created_at, key->relay);
   1791 }
   1792 
   1793 static int ndb_build_relay_kind_key(unsigned char *buf, int bufsize, struct ndb_relay_kind_key *key)
   1794 {
   1795 	struct cursor cur;
   1796 	make_cursor(buf, buf + bufsize, &cur);
   1797 
   1798 	if (!cursor_push(&cur, (unsigned char *)&key->note_key, 8))   return 0;
   1799 	if (!cursor_push(&cur, (unsigned char *)&key->kind, 8))       return 0;
   1800 	if (!cursor_push(&cur, (unsigned char *)&key->created_at, 8)) return 0;
   1801 	if (!cursor_push_byte(&cur, key->relay_len)) return 0;
   1802 	if (!cursor_push(&cur, (unsigned char *)key->relay, key->relay_len)) return 0;
   1803 	if (!cursor_push_byte(&cur, 0)) return 0;
   1804 	if (!cursor_align(&cur, 8)) return 0;
   1805 
   1806 	assert(((cur.p-cur.start)%8) == 0);
   1807 
   1808 	return cur.p - cur.start;
   1809 }
   1810 
   1811 static int ndb_write_note_relay_kind_index(
   1812 		struct ndb_txn *txn,
   1813 		struct ndb_relay_kind_key *key)
   1814 {
   1815 	// The relay kind key has a layout like so 
   1816 	//
   1817 	// - note_key:       00 + 8 bytes
   1818 	// - kind:           08 + 8 bytes
   1819 	// - created_at:     16 + 8 bytes
   1820 	// - relay_url_size: 24 + 1 byte
   1821 	// - relay_url:      25 + n byte null-terminated string
   1822 	// - pad to 8 byte alignment
   1823 
   1824 	unsigned char buf[256];
   1825 	int rc, len;
   1826 	MDB_val k, v;
   1827 
   1828 	// come on bro
   1829 	if (key->relay_len > 248 || key->relay == NULL || key->relay_len == 0)
   1830 		return 0;
   1831 
   1832 	ndb_debug("writing note_relay_kind_index '%s' for notekey:%" PRIu64 "\n", key->relay, key->note_key);
   1833 
   1834 	if (!(len = ndb_build_relay_kind_key(buf, sizeof(buf), key)))
   1835 		return 0;
   1836 
   1837 	k.mv_data = buf;
   1838 	k.mv_size = len;
   1839 
   1840 	v.mv_data = NULL;
   1841 	v.mv_size = 0;
   1842 
   1843 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAY_KIND], &k, &v, 0))) {
   1844 		fprintf(stderr, "write note relay kind index failed: %s\n",
   1845 			  mdb_strerror(rc));
   1846 		return 0;
   1847 	}
   1848 
   1849 	return 1;
   1850 }
   1851 
   1852 // writes the relay note kind index and the note_id -> relay db
   1853 static int ndb_write_note_relay_indexes(struct ndb_txn *txn, struct ndb_relay_kind_key *key)
   1854 {
   1855 	ndb_write_note_relay_kind_index(txn, key);
   1856 	ndb_write_note_relay(txn, key->note_key, key->relay, key->relay_len);
   1857 	return 1;
   1858 }
   1859 
   1860 static int ndb_write_note_pubkey_index(struct ndb_txn *txn, struct ndb_note *note,
   1861 				       uint64_t note_key)
   1862 {
   1863 	int rc;
   1864 	struct ndb_tsid key;
   1865 	MDB_val k, v;
   1866 
   1867 	ndb_tsid_init(&key, ndb_note_pubkey(note), ndb_note_created_at(note));
   1868 
   1869 	k.mv_data = &key;
   1870 	k.mv_size = sizeof(key);
   1871 
   1872 	v.mv_data = &note_key;
   1873 	v.mv_size = sizeof(note_key);
   1874 
   1875 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY], &k, &v, 0))) {
   1876 		fprintf(stderr, "write note pubkey index failed: %s\n",
   1877 			  mdb_strerror(rc));
   1878 		return 0;
   1879 	}
   1880 
   1881 	return 1;
   1882 }
   1883 
   1884 static int ndb_write_note_pubkey_kind_index(struct ndb_txn *txn,
   1885 					    struct ndb_note *note,
   1886 					    uint64_t note_key)
   1887 {
   1888 	int rc;
   1889 	struct ndb_id_u64_ts key;
   1890 	MDB_val k, v;
   1891 
   1892 	ndb_id_u64_ts_init(&key, ndb_note_pubkey(note), ndb_note_kind(note),
   1893 			   ndb_note_created_at(note));
   1894 
   1895 	k.mv_data = &key;
   1896 	k.mv_size = sizeof(key);
   1897 
   1898 	v.mv_data = &note_key;
   1899 	v.mv_size = sizeof(note_key);
   1900 
   1901 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND], &k, &v, 0))) {
   1902 		fprintf(stderr, "write note pubkey_kind index failed: %s\n",
   1903 			  mdb_strerror(rc));
   1904 		return 0;
   1905 	}
   1906 
   1907 	return 1;
   1908 }
   1909 
   1910 
   1911 static int ndb_rebuild_note_indices(struct ndb_txn *txn, enum ndb_dbs *indices, int num_indices)
   1912 {
   1913 	MDB_val k, v;
   1914 	MDB_cursor *cur;
   1915 	int i, drop_dbi, count, rc;
   1916 	uint64_t note_key;
   1917 	struct ndb_note *note;
   1918 	enum ndb_dbs index;
   1919 
   1920 	// 0 means empty, not delete the dbi
   1921 	drop_dbi = 0;
   1922 
   1923 	// ensure they are all index dbs
   1924 	for (i = 0; i < num_indices; i++) {
   1925 		if (!ndb_db_is_index(indices[i])) {
   1926 			fprintf(stderr, "ndb_rebuild_note_indices: %s is not an index db\n", ndb_db_name(indices[i]));
   1927 			return -1;
   1928 		}
   1929 	}
   1930 
   1931 	// empty the index dbs before we rebuild
   1932 	for (i = 0; i < num_indices; i++) {
   1933 		index = indices[i];
   1934 		if (mdb_drop(txn->mdb_txn, index, drop_dbi)) {
   1935 			fprintf(stderr, "ndb_rebuild_note_indices: mdb_drop failed for %s\n", ndb_db_name(index));
   1936 			return -1;
   1937 		}
   1938 	}
   1939 
   1940 	if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE], &cur))) {
   1941 		fprintf(stderr, "ndb_rebuild_note_indices: mdb_cursor_open failed, error %d\n", rc);
   1942 		return -1;
   1943 	}
   1944 
   1945 	count = 0;
   1946 
   1947 	// loop through all notes and write search indices
   1948 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   1949 		note = v.mv_data;
   1950 		note_key = *((uint64_t*)k.mv_data);
   1951 
   1952 		for (i = 0; i < num_indices; i++) {
   1953 			index = indices[i];
   1954 			switch (index) {
   1955 			case NDB_DB_NOTE:
   1956 			case NDB_DB_META:
   1957 			case NDB_DB_PROFILE:
   1958 			case NDB_DB_NOTE_ID:
   1959 			case NDB_DB_NDB_META:
   1960 			case NDB_DB_PROFILE_SEARCH:
   1961 			case NDB_DB_PROFILE_LAST_FETCH:
   1962 			case NDB_DB_NOTE_RELAYS:
   1963 			case NDB_DBS:
   1964 				// this should never happen since we check at
   1965 				// the start
   1966 				count = -1;
   1967 				goto cleanup;
   1968 			case NDB_DB_PROFILE_PK:
   1969 			case NDB_DB_NOTE_KIND:
   1970 			case NDB_DB_NOTE_TEXT:
   1971 			case NDB_DB_NOTE_BLOCKS:
   1972 			case NDB_DB_NOTE_TAGS:
   1973 				fprintf(stderr, "%s index rebuild not supported yet. sorry.\n", ndb_db_name(index));
   1974 				count = -1;
   1975 				goto cleanup;
   1976 			case NDB_DB_NOTE_PUBKEY:
   1977 				if (!ndb_write_note_pubkey_index(txn, note, note_key)) {
   1978 					count = -1;
   1979 					goto cleanup;
   1980 				}
   1981 				break;
   1982 			case NDB_DB_NOTE_RELAY_KIND:
   1983 				fprintf(stderr, "it doesn't make sense to rebuild note relay kind index\n");
   1984 				return 0;
   1985 			case NDB_DB_NOTE_PUBKEY_KIND:
   1986 				if (!ndb_write_note_pubkey_kind_index(txn, note, note_key)) {
   1987 					count = -1;
   1988 					goto cleanup;
   1989 				}
   1990 				break;
   1991 			}
   1992 		}
   1993 
   1994 		count++;
   1995 	}
   1996 
   1997 cleanup:
   1998 	mdb_cursor_close(cur);
   1999 
   2000 	return count;
   2001 }
   2002 
   2003 int ndb_cursor_start(MDB_cursor *cur, MDB_val *k, MDB_val *v);
   2004 
   2005 // find the last id tag in a note (e, p, etc)
   2006 static unsigned char *ndb_note_last_id_tag(struct ndb_note *note, char type)
   2007 {
   2008 	unsigned char *last = NULL;
   2009 	struct ndb_iterator iter;
   2010 	struct ndb_str str;
   2011 
   2012 	// get the liked event id (last id)
   2013 	ndb_tags_iterate_start(note, &iter);
   2014 
   2015 	while (ndb_tags_iterate_next(&iter)) {
   2016 		if (iter.tag->count < 2)
   2017 			continue;
   2018 
   2019 		str = ndb_tag_str(note, iter.tag, 0);
   2020 
   2021 		// assign liked to the last e tag
   2022 		if (str.flag == NDB_PACKED_STR && str.str[0] == type) {
   2023 			str = ndb_tag_str(note, iter.tag, 1);
   2024 			if (str.flag == NDB_PACKED_ID)
   2025 				last = str.id;
   2026 		}
   2027 	}
   2028 
   2029 	return last;
   2030 }
   2031 
   2032 /* get reply information from a note */
   2033 static void ndb_parse_reply(struct ndb_note *note, struct ndb_note_reply *note_reply)
   2034 {
   2035 	unsigned char *root, *reply, *mention, *id;
   2036 	const char *marker;
   2037 	struct ndb_iterator iter;
   2038 	struct ndb_str str;
   2039 	uint16_t count;
   2040 	int any_marker, first;
   2041 
   2042 	any_marker = 0;
   2043 	first = 1;
   2044 	root = NULL;
   2045 	reply = NULL;
   2046 	mention = NULL;
   2047 
   2048 	// get the liked event id (last id)
   2049 	ndb_tags_iterate_start(note, &iter);
   2050 	while (ndb_tags_iterate_next(&iter)) {
   2051 		if (root && reply && mention)
   2052 			break;
   2053 
   2054 		marker = NULL;
   2055 		count = ndb_tag_count(iter.tag);
   2056 
   2057 		if (count < 2)
   2058 			continue;
   2059 
   2060 		str = ndb_tag_str(note, iter.tag, 0);
   2061 		if (!(str.flag == NDB_PACKED_STR && str.str[0] == 'e'))
   2062 			continue;
   2063 
   2064 		str = ndb_tag_str(note, iter.tag, 1);
   2065 		if (str.flag != NDB_PACKED_ID)
   2066 			continue;
   2067 		id = str.id;
   2068 
   2069 		/* if we have the marker, assign it */
   2070 		if (count >= 4) {
   2071 			str = ndb_tag_str(note, iter.tag, 3);
   2072 			if (str.flag == NDB_PACKED_STR)
   2073 				marker = str.str;
   2074 		}
   2075 
   2076 		if (marker) {
   2077 			any_marker = true;
   2078 			if (!strcmp(marker, "root"))
   2079 				root = id;
   2080 			else if (!strcmp(marker, "reply"))
   2081 				reply = id;
   2082 			else if (!strcmp(marker, "mention"))
   2083 				mention = id;
   2084 		} else if (!any_marker && first) {
   2085 			root = id;
   2086 			first = 0;
   2087 		} else if (!any_marker && !reply) {
   2088 			reply = id;
   2089 		}
   2090 	}
   2091 
   2092 	note_reply->reply = reply;
   2093 	note_reply->root = root;
   2094 	note_reply->mention = mention;
   2095 }
   2096 
   2097 static int ndb_is_reply_to_root(struct ndb_note_reply *reply)
   2098 {
   2099 	if (reply->root && !reply->reply)
   2100 		return 1;
   2101 	else if (reply->root && reply->reply)
   2102 		return !memcmp(reply->root, reply->reply, 32);
   2103 	else
   2104 		return 0;
   2105 }
   2106 
   2107 
   2108 int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies)
   2109 {
   2110 	MDB_val k, v;
   2111 	MDB_cursor *cur;
   2112 	MDB_dbi db;
   2113 
   2114 	int rc;
   2115 	uint64_t note_key;
   2116 	size_t size;
   2117 	struct ndb_note *note;
   2118 	unsigned char *keybuf, *reply_id;
   2119 	struct ndb_note_reply reply;
   2120 	char buffer[41]; /* 1 + 32 + 8 */
   2121 
   2122 	*direct_replies = 0;
   2123 	*thread_replies = 0;
   2124 
   2125 	db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
   2126 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) {
   2127 		fprintf(stderr, "ndb_count_reactions: mdb_cursor_open failed, error %d\n", rc);
   2128 		return 0;
   2129 	}
   2130 
   2131 	buffer[0] = 'e';
   2132 	memcpy(&buffer[1], note_id, 32);
   2133 	memset(&buffer[33], 0x00, 8);
   2134 
   2135 	k.mv_data = buffer;
   2136 	k.mv_size = sizeof(buffer);
   2137 	v.mv_data = NULL;
   2138 	v.mv_size = 0;
   2139 
   2140 	if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE))
   2141 		goto cleanup;
   2142 
   2143 	do {
   2144 		keybuf = (unsigned char *)k.mv_data;
   2145 		note_key = *((uint64_t*)v.mv_data);
   2146 		if (k.mv_size < sizeof(buffer))
   2147 			break;
   2148 		if (keybuf[0] != 'e')
   2149 			break;
   2150 		if (memcmp(&keybuf[1], note_id, 32))
   2151 			break;
   2152 		if (!(note = ndb_get_note_by_key(txn, note_key, &size)))
   2153 			continue;
   2154 		if (ndb_note_kind(note) != 1)
   2155 			continue;
   2156 
   2157 		ndb_parse_reply(note, &reply);
   2158 
   2159 		if (ndb_is_reply_to_root(&reply)) {
   2160 			reply_id = reply.root;
   2161 		} else {
   2162 			reply_id = reply.reply;
   2163 		}
   2164 
   2165 		if (reply_id && !memcmp(reply_id, note_id, 32)) {
   2166 			(*direct_replies)++;
   2167 		}
   2168 
   2169 		if (reply.root && !memcmp(reply.root, note_id, 32)) {
   2170 			(*thread_replies)++;
   2171 		}
   2172 
   2173 	} while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0);
   2174 
   2175 cleanup:
   2176 	mdb_cursor_close(cur);
   2177 	return 1;
   2178 }
   2179 
   2180 /* count all of the reactions for a note */
   2181 int ndb_rebuild_reaction_metadata(struct ndb_txn *txn, const unsigned char *note_id, struct ndb_note_meta_builder *builder, uint32_t *count)
   2182 {
   2183 	MDB_val k, v;
   2184 	MDB_cursor *cur;
   2185 	MDB_dbi db;
   2186 
   2187 	int rc;
   2188 	uint64_t note_key;
   2189 	size_t size;
   2190 	struct ndb_note *note;
   2191 	unsigned char *keybuf, *last_id;
   2192 	struct ndb_note_meta_entry *entry;
   2193 	union ndb_reaction_str reaction_str;
   2194 	char buffer[41]; /* 1 + 32 + 8 */
   2195 	*count = 0;
   2196 
   2197 	db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
   2198 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) {
   2199 		fprintf(stderr, "ndb_count_reactions: mdb_cursor_open failed, error %d\n", rc);
   2200 		return 0;
   2201 	}
   2202 
   2203 	buffer[0] = 'e';
   2204 	memcpy(&buffer[1], note_id, 32);
   2205 	memset(&buffer[33], 0x00, 8);
   2206 
   2207 	k.mv_data = buffer;
   2208 	k.mv_size = sizeof(buffer);
   2209 	v.mv_data = NULL;
   2210 	v.mv_size = 0;
   2211 
   2212 	if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE))
   2213 		goto cleanup;
   2214 
   2215 	do {
   2216 		keybuf = (unsigned char *)k.mv_data;
   2217 		note_key = *((uint64_t*)v.mv_data);
   2218 		if (k.mv_size < sizeof(buffer))
   2219 			break;
   2220 		if (keybuf[0] != 'e')
   2221 			break;
   2222 		if (memcmp(&keybuf[1], note_id, 32))
   2223 			break;
   2224 		if (!(note = ndb_get_note_by_key(txn, note_key, &size)))
   2225 			continue;
   2226 		if (ndb_note_kind(note) != 7)
   2227 			continue;
   2228 		if (!(last_id = ndb_note_last_id_tag(note, 'e')))
   2229 			continue;
   2230 		if (memcmp(last_id, note_id, 32))
   2231 			continue;
   2232 
   2233 		if (builder) {
   2234 			if (!ndb_reaction_set(&reaction_str, ndb_note_content(note)))
   2235 				ndb_reaction_set(&reaction_str, "+");
   2236 
   2237 			if ((entry = ndb_note_meta_builder_find_entry(builder, NDB_NOTE_META_REACTION, &reaction_str.binmoji))) {
   2238 				(*ndb_note_meta_reaction_count(entry))++;
   2239 			} else if ((entry = ndb_note_meta_add_entry(builder))) {
   2240 				ndb_note_meta_reaction_set(entry, 1, reaction_str);
   2241 			} else {
   2242 				/* couldn't add reaction entry ? */
   2243 				ndb_debug("ndb_rebuild_note_indices: couldn't add reaction count entry to metadata builder\n");
   2244 			}
   2245 		}
   2246 
   2247 		(*count)++;
   2248 	} while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0);
   2249 
   2250 cleanup:
   2251 	mdb_cursor_close(cur);
   2252 	return 1;
   2253 }
   2254 
   2255 static int ndb_count_reposts(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count)
   2256 {
   2257 	MDB_val k, v;
   2258 	MDB_cursor *cur;
   2259 	MDB_dbi db;
   2260 	int rc;
   2261 	unsigned char *keybuf;
   2262 	struct ndb_note *note;
   2263 	uint64_t note_key, kind;
   2264 	char buffer[41]; /* 1 + 32 + 8 */
   2265 
   2266 	*count = 0;
   2267 	db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
   2268 
   2269 	/* we will iterate q tags for this particular id */
   2270 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) {
   2271 		fprintf(stderr, "ndb_count_quotes: mdb_cursor_open failed, error %d\n", rc);
   2272 		return 0;
   2273 	}
   2274 
   2275 	buffer[0] = 'e';
   2276 	memcpy(&buffer[1], note_id, 32);
   2277 	memset(&buffer[33], 0x00, 8);
   2278 
   2279 	k.mv_data = buffer;
   2280 	k.mv_size = sizeof(buffer);
   2281 	v.mv_data = NULL;
   2282 	v.mv_size = 0;
   2283 
   2284 	if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE))
   2285 		goto cleanup;
   2286 
   2287 	do {
   2288 		keybuf = (unsigned char *)k.mv_data;
   2289 		note_key = *((uint64_t*)v.mv_data);
   2290 
   2291 		if (k.mv_size < sizeof(buffer))
   2292 			break;
   2293 		if (keybuf[0] != 'e')
   2294 			break;
   2295 		if (memcmp(&keybuf[1], note_id, 32) != 0)
   2296 			break;
   2297 		if (!(note = ndb_get_note_by_key(txn, note_key, NULL)))
   2298 			continue;
   2299 		kind = ndb_note_kind(note);
   2300 		if (!(kind == 6 || kind == 16))
   2301 			continue;
   2302 		(*count)++;
   2303 	} while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0);
   2304 
   2305 cleanup:
   2306 	mdb_cursor_close(cur);
   2307 	return 1;
   2308 }
   2309 
   2310 /* count all of the quote reposts for a note id */
   2311 static int ndb_count_quotes(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count)
   2312 {
   2313 	MDB_val k, v;
   2314 	MDB_cursor *cur;
   2315 	MDB_dbi db;
   2316 	int rc;
   2317 	unsigned char *keybuf;
   2318 	char buffer[41]; /* 1 + 32 + 8 */
   2319 
   2320 	*count = 0;
   2321 	db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
   2322 
   2323 	/* we will iterate q tags for this particular id */
   2324 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) {
   2325 		fprintf(stderr, "ndb_count_quotes: mdb_cursor_open failed, error %d\n", rc);
   2326 		return 0;
   2327 	}
   2328 
   2329 	buffer[0] = 'q';
   2330 	memcpy(&buffer[1], note_id, 32);
   2331 	memset(&buffer[33], 0x00, 8);
   2332 
   2333 	k.mv_data = buffer;
   2334 	k.mv_size = sizeof(buffer);
   2335 	v.mv_data = NULL;
   2336 	v.mv_size = 0;
   2337 
   2338 	if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE))
   2339 		goto cleanup;
   2340 
   2341 	for (;;) {
   2342 		keybuf = (unsigned char *)k.mv_data;
   2343 		if (k.mv_size < sizeof(buffer))
   2344 			break;
   2345 		if (keybuf[0] != 'q')
   2346 			break;
   2347 		if (memcmp(&keybuf[1], note_id, 32) != 0)
   2348 			break;
   2349 		/* TODO(jb55): technically we should check to see if this is a kind 1.
   2350 		 * there could be other kinds with q tags that reference this note
   2351 		 *
   2352 		 * Starting to think we should have tag-kind index
   2353 		 */
   2354 		(*count)++;
   2355 
   2356 		if (mdb_cursor_get(cur, &k, &v, MDB_NEXT))
   2357 			break;
   2358 	}
   2359 
   2360 cleanup:
   2361 	mdb_cursor_close(cur);
   2362 	return 1;
   2363 }
   2364 
   2365 /* count quotes and add them to a metadata builder.
   2366  * we assume there is no existing quotes entry */
   2367 static int ndb_note_meta_builder_counts(struct ndb_txn *txn,
   2368 					unsigned char *note_id,
   2369 					struct ndb_note_meta_builder *builder)
   2370 {
   2371 	uint32_t thread_replies, total_reactions;
   2372 	uint16_t direct_replies, quotes, reposts;
   2373 	struct ndb_note_meta_entry *entry;
   2374 	int rcs[4];
   2375 
   2376 	quotes = 0;
   2377 	direct_replies = 0;
   2378 	thread_replies = 0;
   2379 	total_reactions = 0;
   2380 
   2381 	rcs[0] = ndb_rebuild_reaction_metadata(txn, note_id, builder, &total_reactions);
   2382 	rcs[1] = ndb_count_quotes(txn, note_id, &quotes);
   2383 	rcs[2] = ndb_count_replies(txn, note_id, &direct_replies, &thread_replies);
   2384 	rcs[3] = ndb_count_reposts(txn, note_id, &reposts);
   2385 
   2386 	if (!rcs[0] && !rcs[1] && !rcs[2] && !rcs[3]) {
   2387 		return 0;
   2388 	}
   2389 
   2390 	/* no entry needed */
   2391 	if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0 && reposts == 0) {
   2392 		return 0;
   2393 	}
   2394 
   2395 	if (!(entry = ndb_note_meta_add_entry(builder))) {
   2396 		return 0;
   2397 	}
   2398 
   2399 	ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies, reposts);
   2400 
   2401 	return 1;
   2402 }
   2403 
   2404 /* count all of the reactions on a note and add it to metadata in progress */
   2405 static void ndb_note_meta_builder_count_reactions(struct ndb_txn *txn, struct ndb_note_meta_builder *builder)
   2406 {
   2407 }
   2408 
   2409 
   2410 // Migrations
   2411 //
   2412 
   2413 /* switch from flatbuffer stats to custom v2 */
   2414 static int ndb_migrate_metadata(struct ndb_txn *txn)
   2415 {
   2416 	MDB_val k, k2, v, v2;
   2417 	MDB_cursor *cur;
   2418 	MDB_dbi note_db, meta_db;
   2419 	unsigned char *id;
   2420 	size_t scratch_size = 1024 * 1024;
   2421 	unsigned char *buffer = malloc(scratch_size);
   2422 	int rc, count;
   2423 	struct ndb_note_meta_builder builder;
   2424 	struct ndb_note *note;
   2425 	struct ndb_note_meta *meta;
   2426 
   2427 	meta_db = txn->lmdb->dbs[NDB_DB_META];
   2428 	note_db = txn->lmdb->dbs[NDB_DB_NOTE];
   2429 
   2430 	/* drop metadata table to avoid issues */
   2431 	if (mdb_drop(txn->mdb_txn, meta_db, 0)) {
   2432 		fprintf(stderr, "ndb_migrate_metadata: mdb_drop failed\n");
   2433 		return -1;
   2434 	}
   2435 
   2436 	if ((rc = mdb_cursor_open(txn->mdb_txn, note_db, &cur))) {
   2437 		fprintf(stderr, "ndb_migrate_metadata: mdb_cursor_open failed, error %d\n", rc);
   2438 		return -1;
   2439 	}
   2440 
   2441 	count = 0;
   2442 
   2443 	/* loop through every metadata entry */
   2444 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   2445 		ndb_note_meta_builder_init(&builder, buffer, scratch_size);
   2446 
   2447 		note = (struct ndb_note *)v.mv_data;
   2448 		id = ndb_note_id(note);
   2449 		k2.mv_data = (unsigned char *)id;
   2450 		k2.mv_size = 32;
   2451 
   2452 		rc = ndb_note_meta_builder_counts(txn, id, &builder);
   2453 		if (!rc) {
   2454 			mdb_del(txn->mdb_txn, meta_db, &k2, NULL);
   2455 			continue;
   2456 		}
   2457 
   2458 		ndb_note_meta_build(&builder, &meta);
   2459 		assert(ndb_note_meta_entries(meta)->type != 0);
   2460 
   2461 		v2.mv_data = meta;
   2462 		v2.mv_size = ndb_note_meta_total_size(meta);
   2463 
   2464 		/* set entry */
   2465 		if ((rc = mdb_put(txn->mdb_txn, meta_db, &k2, &v2, 0))) {
   2466 			ndb_debug("migrate metadata entry failed on write: %s\n", mdb_strerror(rc));
   2467 		}
   2468 
   2469 		count++;
   2470 	}
   2471 
   2472 	fprintf(stderr, "nostrdb: migrated %d metadata entries\n", count);
   2473 
   2474 	free(buffer);
   2475 	mdb_cursor_close(cur);
   2476 	return 1;
   2477 }
   2478 
   2479 // This was before we had note_profile_pubkey{,_kind} indices. Let's create them.
   2480 static int ndb_migrate_profile_indices(struct ndb_txn *txn)
   2481 {
   2482 	int count;
   2483 
   2484 	enum ndb_dbs indices[] = {NDB_DB_NOTE_PUBKEY, NDB_DB_NOTE_PUBKEY_KIND};
   2485 	if ((count = ndb_rebuild_note_indices(txn, indices, 2)) != -1) {
   2486 		fprintf(stderr, "migrated %d notes to have pubkey and pubkey_kind indices\n", count);
   2487 		return 1;
   2488 	} else {
   2489 		fprintf(stderr, "error migrating notes to have pubkey and pubkey_kind indices, aborting.\n");
   2490 		return 0;
   2491 	}
   2492 }
   2493 
   2494 static int ndb_migrate_user_search_indices(struct ndb_txn *txn)
   2495 {
   2496 	int rc;
   2497 	MDB_cursor *cur;
   2498 	MDB_val k, v;
   2499 	void *profile_root;
   2500 	NdbProfileRecord_table_t record;
   2501 	struct ndb_note *note;
   2502 	uint64_t note_key, profile_key;
   2503 	size_t len;
   2504 	int count;
   2505 
   2506 	if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE], &cur))) {
   2507 		fprintf(stderr, "ndb_migrate_user_search_indices: mdb_cursor_open failed, error %d\n", rc);
   2508 		return 0;
   2509 	}
   2510 
   2511 	count = 0;
   2512 
   2513 	// loop through all profiles and write search indices
   2514 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   2515 		profile_root = v.mv_data;
   2516 		profile_key = *((uint64_t*)k.mv_data);
   2517 		record = NdbProfileRecord_as_root(profile_root);
   2518 		note_key = NdbProfileRecord_note_key(record);
   2519 		note = ndb_get_note_by_key(txn, note_key, &len);
   2520 
   2521 		if (note == NULL) {
   2522 			continue;
   2523 		}
   2524 
   2525 		if (!ndb_write_profile_search_indices(txn, note, profile_key,
   2526 						      profile_root)) {
   2527 			fprintf(stderr, "ndb_migrate_user_search_indices: ndb_write_profile_search_indices failed\n");
   2528 			continue;
   2529 		}
   2530 
   2531 		count++;
   2532 	}
   2533 
   2534 	fprintf(stderr, "migrated %d profiles to include search indices\n", count);
   2535 
   2536 	mdb_cursor_close(cur);
   2537 
   2538 	return 1;
   2539 }
   2540 
   2541 static int ndb_migrate_lower_user_search_indices(struct ndb_txn *txn)
   2542 {
   2543 	// just drop the search db so we can rebuild it
   2544 	if (mdb_drop(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_SEARCH], 0)) {
   2545 		fprintf(stderr, "ndb_migrate_lower_user_search_indices: mdb_drop failed\n");
   2546 		return 0;
   2547 	}
   2548 
   2549 	return ndb_migrate_user_search_indices(txn);
   2550 }
   2551 
   2552 int ndb_process_profile_note(struct ndb_note *note, struct ndb_profile_record_builder *profile);
   2553 
   2554 
   2555 int ndb_db_version(struct ndb_txn *txn)
   2556 {
   2557 	uint64_t version, version_key;
   2558 	MDB_val k, v;
   2559 
   2560 	version_key = NDB_META_KEY_VERSION;
   2561 	k.mv_data = &version_key;
   2562 	k.mv_size = sizeof(version_key);
   2563 
   2564 	if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NDB_META], &k, &v)) {
   2565 		version = -1;
   2566 	} else {
   2567 		if (v.mv_size != 8) {
   2568 			fprintf(stderr, "run_migrations: invalid version size?");
   2569 			return 0;
   2570 		}
   2571 		version = *((uint64_t*)v.mv_data);
   2572 	}
   2573 
   2574 	return version;
   2575 }
   2576 
   2577 // custom pubkey+kind+timestamp comparison function. This is used by lmdb to
   2578 // perform b+ tree searches over the pubkey+kind+timestamp index
   2579 static int ndb_id_u64_ts_compare(const MDB_val *a, const MDB_val *b)
   2580 {
   2581 	struct ndb_id_u64_ts *tsa, *tsb;
   2582 	MDB_val a2 = *a, b2 = *b;
   2583 
   2584 	a2.mv_size = sizeof(tsa->id);
   2585 	b2.mv_size = sizeof(tsb->id);
   2586 
   2587 	int cmp = mdb_cmp_memn(&a2, &b2);
   2588 	if (cmp) return cmp;
   2589 
   2590 	tsa = a->mv_data;
   2591 	tsb = b->mv_data;
   2592 
   2593 	if (tsa->u64 < tsb->u64)
   2594 		return -1;
   2595 	else if (tsa->u64 > tsb->u64)
   2596 		return 1;
   2597 
   2598 	if (tsa->timestamp < tsb->timestamp)
   2599 		return -1;
   2600 	else if (tsa->timestamp > tsb->timestamp)
   2601 		return 1;
   2602 
   2603 	return 0;
   2604 }
   2605 
   2606 // custom kind+timestamp comparison function. This is used by lmdb to perform
   2607 // b+ tree searches over the kind+timestamp index
   2608 static int ndb_u64_ts_compare(const MDB_val *a, const MDB_val *b)
   2609 {
   2610 	struct ndb_u64_ts *tsa, *tsb;
   2611 	tsa = a->mv_data;
   2612 	tsb = b->mv_data;
   2613 
   2614 	if (tsa->u64 < tsb->u64)
   2615 		return -1;
   2616 	else if (tsa->u64 > tsb->u64)
   2617 		return 1;
   2618 
   2619 	if (tsa->timestamp < tsb->timestamp)
   2620 		return -1;
   2621 	else if (tsa->timestamp > tsb->timestamp)
   2622 		return 1;
   2623 
   2624 	return 0;
   2625 }
   2626 
   2627 static int ndb_tsid_compare(const MDB_val *a, const MDB_val *b)
   2628 {
   2629 	struct ndb_tsid *tsa, *tsb;
   2630 	MDB_val a2 = *a, b2 = *b;
   2631 
   2632 	a2.mv_size = sizeof(tsa->id);
   2633 	b2.mv_size = sizeof(tsb->id);
   2634 
   2635 	int cmp = mdb_cmp_memn(&a2, &b2);
   2636 	if (cmp) return cmp;
   2637 
   2638 	tsa = a->mv_data;
   2639 	tsb = b->mv_data;
   2640 
   2641 	if (tsa->timestamp < tsb->timestamp)
   2642 		return -1;
   2643 	else if (tsa->timestamp > tsb->timestamp)
   2644 		return 1;
   2645 	return 0;
   2646 }
   2647 
   2648 enum ndb_ingester_msgtype {
   2649 	NDB_INGEST_EVENT, // write json to the ingester queue for processing
   2650 	NDB_INGEST_QUIT,  // kill ingester thread immediately
   2651 };
   2652 
   2653 struct ndb_ingester_event {
   2654 	const char *relay;
   2655 	char *json;
   2656 	unsigned client : 1; // ["EVENT", {...}] messages
   2657 	unsigned len : 31;
   2658 };
   2659 
   2660 struct ndb_writer_note_relay {
   2661 	const char *relay;
   2662 	uint64_t note_key;
   2663 	uint64_t kind;
   2664 	uint64_t created_at;
   2665 };
   2666 
   2667 struct ndb_writer_note {
   2668 	struct ndb_note *note;
   2669 	size_t note_len;
   2670 	const char *relay;
   2671 };
   2672 
   2673 static void ndb_writer_note_init(struct ndb_writer_note *writer_note, struct ndb_note *note, size_t note_len, const char *relay)
   2674 {
   2675 	writer_note->note = note;
   2676 	writer_note->note_len = note_len;
   2677 	writer_note->relay = relay;
   2678 }
   2679 
   2680 struct ndb_writer_profile {
   2681 	struct ndb_writer_note note;
   2682 	struct ndb_profile_record_builder record;
   2683 };
   2684 
   2685 struct ndb_ingester_msg {
   2686 	enum ndb_ingester_msgtype type;
   2687 	union {
   2688 		struct ndb_ingester_event event;
   2689 	};
   2690 };
   2691 
   2692 struct ndb_writer_ndb_meta {
   2693 	// these are 64 bit because I'm paranoid of db-wide alignment issues
   2694 	uint64_t version;
   2695 };
   2696 
   2697 struct ndb_writer_note_meta {
   2698 	unsigned char note_id[32];
   2699 	struct ndb_note_meta *metadata;
   2700 };
   2701 
   2702 // Used in the writer thread when writing ndb_profile_fetch_record's
   2703 //   kv = pubkey: recor
   2704 struct ndb_writer_last_fetch {
   2705 	unsigned char pubkey[32];
   2706 	uint64_t fetched_at;
   2707 };
   2708 
   2709 // write note blocks
   2710 struct ndb_writer_blocks {
   2711 	struct ndb_blocks *blocks;
   2712 	uint64_t note_key;
   2713 };
   2714 
   2715 // The different types of messages that the writer thread can write to the
   2716 // database
   2717 struct ndb_writer_msg {
   2718 	enum ndb_writer_msgtype type;
   2719 	union {
   2720 		struct ndb_writer_note_relay note_relay;
   2721 		struct ndb_writer_note note;
   2722 		struct ndb_writer_profile profile;
   2723 		struct ndb_writer_ndb_meta ndb_meta;
   2724 		struct ndb_writer_last_fetch last_fetch;
   2725 		struct ndb_writer_blocks blocks;
   2726 		struct ndb_writer_note_meta note_meta;
   2727 	};
   2728 };
   2729 
   2730 static inline int ndb_writer_queue_msg(struct ndb_writer *writer,
   2731 				       struct ndb_writer_msg *msg)
   2732 {
   2733 	return prot_queue_push(&writer->inbox, msg);
   2734 }
   2735 
   2736 static uint64_t ndb_write_note_and_profile(struct ndb_txn *txn, struct ndb_writer_profile *profile, unsigned char *scratch, size_t scratch_size, uint32_t ndb_flags);
   2737 static int ndb_migrate_utf8_profile_names(struct ndb_txn *txn)
   2738 {
   2739 	int rc;
   2740 	MDB_cursor *cur;
   2741 	MDB_val k, v;
   2742 	void *profile_root;
   2743 	NdbProfileRecord_table_t record;
   2744 	struct ndb_note *note, *copied_note;
   2745 	uint64_t note_key;
   2746 	size_t len;
   2747 	int count, failed, ret;
   2748 	struct ndb_writer_profile profile;
   2749 
   2750 	if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE], &cur))) {
   2751 		fprintf(stderr, "ndb_migrate_utf8_profile_names: mdb_cursor_open failed, error %d\n", rc);
   2752 		return 0;
   2753 	}
   2754 
   2755 	size_t scratch_size = 8 * 1024 * 1024;
   2756 	unsigned char *scratch = malloc(scratch_size);
   2757 
   2758 	ret = 1;
   2759 	count = 0;
   2760 	failed = 0;
   2761 
   2762 	// loop through all profiles and write search indices
   2763 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   2764 		profile_root = v.mv_data;
   2765 		record = NdbProfileRecord_as_root(profile_root);
   2766 		note_key = NdbProfileRecord_note_key(record);
   2767 		note = ndb_get_note_by_key(txn, note_key, &len);
   2768 
   2769 		if (note == NULL) {
   2770 			failed++;
   2771 			continue;
   2772 		}
   2773 
   2774 		struct ndb_profile_record_builder *b = &profile.record;
   2775 
   2776 		// reprocess profile
   2777 		if (!ndb_process_profile_note(note, b)) {
   2778 			failed++;
   2779 			continue;
   2780 		}
   2781 
   2782 		// the writer needs to own this note, and its expected to free it
   2783 		copied_note = malloc(len);
   2784 		memcpy(copied_note, note, len);
   2785 
   2786 		ndb_writer_note_init(&profile.note, copied_note, len, NULL);
   2787 
   2788 		// we don't pass in flags when migrating... a bit sketchy but
   2789 		// whatever. noone is using this to customize nostrdb atm
   2790 		if (ndb_write_note_and_profile(txn, &profile, scratch, scratch_size, 0)) {
   2791 			count++;
   2792 		}
   2793 	}
   2794 
   2795 	fprintf(stderr, "migrated %d profiles to fix utf8 profile names\n", count);
   2796 
   2797 	if (failed != 0) {
   2798 		fprintf(stderr, "failed to migrate %d profiles to fix utf8 profile names\n", failed);
   2799 	}
   2800 
   2801 	free(scratch);
   2802 	mdb_cursor_close(cur);
   2803 
   2804 	return ret;
   2805 }
   2806 
   2807 static struct ndb_migration MIGRATIONS[] = {
   2808 	{ .fn = ndb_migrate_user_search_indices },
   2809 	{ .fn = ndb_migrate_lower_user_search_indices },
   2810 	{ .fn = ndb_migrate_utf8_profile_names },
   2811 	{ .fn = ndb_migrate_profile_indices },
   2812 	{ .fn = ndb_migrate_metadata },
   2813 };
   2814 
   2815 
   2816 int ndb_end_query(struct ndb_txn *txn)
   2817 {
   2818 	// this works on read or write queries.
   2819 	return mdb_txn_commit(txn->mdb_txn) == 0;
   2820 }
   2821 
   2822 int ndb_note_verify(void *ctx, unsigned char *scratch, size_t scratch_size,
   2823 		    struct ndb_note *note)
   2824 {
   2825 	unsigned char id[32];
   2826 	secp256k1_xonly_pubkey xonly_pubkey;
   2827 	int ok;
   2828 
   2829 	// first, we ensure the id is valid by calculating the id independently
   2830 	// from what is given to us
   2831 	if (!ndb_calculate_id(note, scratch, scratch_size, id)) {
   2832 		ndb_debug("ndb_note_verify: scratch buffer size too small");
   2833 		return 0;
   2834 	}
   2835 
   2836 	if (memcmp(id, note->id, 32)) {
   2837 		ndb_debug("ndb_note_verify: note id does not match!");
   2838 		return 0;
   2839 	}
   2840 
   2841         // id is ok, let's check signature
   2842 
   2843 	ok = secp256k1_xonly_pubkey_parse((secp256k1_context*)ctx,
   2844 					  &xonly_pubkey,
   2845 					  ndb_note_pubkey(note)) != 0;
   2846 	if (!ok) return 0;
   2847 
   2848 	ok = secp256k1_schnorrsig_verify((secp256k1_context*)ctx,
   2849 					 ndb_note_sig(note), id, 32,
   2850 					 &xonly_pubkey) > 0;
   2851 	if (!ok) return 0;
   2852 
   2853 	return 1;
   2854 }
   2855 
   2856 static void ndb_writer_last_profile_fetch(struct ndb_txn *txn,
   2857 					  const unsigned char *pubkey,
   2858 					  uint64_t fetched_at)
   2859 {
   2860 	int rc;
   2861 	MDB_val key, val;
   2862 
   2863 	key.mv_data = (unsigned char*)pubkey;
   2864 	key.mv_size = 32;
   2865 	val.mv_data = &fetched_at;
   2866 	val.mv_size = sizeof(fetched_at);
   2867 
   2868 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_LAST_FETCH],
   2869 			  &key, &val, 0)))
   2870 	{
   2871 		ndb_debug("write version to ndb_meta failed: %s\n",
   2872 				mdb_strerror(rc));
   2873 		return;
   2874 	}
   2875 
   2876 	//fprintf(stderr, "writing version %" PRIu64 "\n", version);
   2877 }
   2878 
   2879 
   2880 // We just received a profile that we haven't processed yet, but it could
   2881 // be an older one! Make sure we only write last fetched profile if it's a new
   2882 // one
   2883 //
   2884 // To do this, we first check the latest profile in the database. If the
   2885 // created_date for this profile note is newer, then we write a
   2886 // last_profile_fetch record, otherwise we do not.
   2887 //
   2888 // WARNING: This function is only valid when called from the writer thread
   2889 static int ndb_maybe_write_last_profile_fetch(struct ndb_txn *txn,
   2890 					       struct ndb_note *note)
   2891 {
   2892 	size_t len;
   2893 	uint64_t profile_key, note_key;
   2894 	void *root;
   2895 	struct ndb_note *last_profile;
   2896 	NdbProfileRecord_table_t record;
   2897 
   2898 	if ((root = ndb_get_profile_by_pubkey(txn, note->pubkey, &len, &profile_key))) {
   2899 		record = NdbProfileRecord_as_root(root);
   2900 		note_key = NdbProfileRecord_note_key(record);
   2901 		last_profile = ndb_get_note_by_key(txn, note_key, &len);
   2902 		if (last_profile == NULL) {
   2903 			return 0;
   2904 		}
   2905 
   2906 		// found profile, let's see if it's newer than ours
   2907 		if (note->created_at > last_profile->created_at) {
   2908 			// this is a new profile note, record last fetched time
   2909 			ndb_writer_last_profile_fetch(txn, note->pubkey, time(NULL));
   2910 		}
   2911 	} else {
   2912 		// couldn't fetch profile. record last fetched time
   2913 		ndb_writer_last_profile_fetch(txn, note->pubkey, time(NULL));
   2914 	}
   2915 
   2916 	return 1;
   2917 }
   2918 
   2919 int ndb_write_last_profile_fetch(struct ndb *ndb, const unsigned char *pubkey,
   2920 				 uint64_t fetched_at)
   2921 {
   2922 	struct ndb_writer_msg msg;
   2923 	msg.type = NDB_WRITER_PROFILE_LAST_FETCH;
   2924 	memcpy(&msg.last_fetch.pubkey[0], pubkey, 32);
   2925 	msg.last_fetch.fetched_at = fetched_at;
   2926 
   2927 	return ndb_writer_queue_msg(&ndb->writer, &msg);
   2928 }
   2929 
   2930 
   2931 // When doing cursor scans from greatest to lowest, this function positions the
   2932 // cursor at the first element before descending. MDB_SET_RANGE puts us right
   2933 // after the first element, so we have to go back one.
   2934 int ndb_cursor_start(MDB_cursor *cur, MDB_val *k, MDB_val *v)
   2935 {
   2936 	int rc;
   2937 	// Position cursor at the next key greater than or equal to the
   2938 	// specified key
   2939 
   2940 	if ((rc = mdb_cursor_get(cur, k, v, MDB_SET_RANGE))) {
   2941 		ndb_debug("MDB_SET_RANGE failed: '%s'\n", mdb_strerror(rc));
   2942 		// Failed :(. It could be the last element?
   2943 		if ((rc = mdb_cursor_get(cur, k, v, MDB_LAST))) {
   2944 			ndb_debug("MDB_LAST failed: '%s'\n", mdb_strerror(rc));
   2945 			return 0;
   2946 		}
   2947 	} else {
   2948 		// if set range worked and our key exists, it should be
   2949 		// the one right before this one
   2950 		if ((rc = mdb_cursor_get(cur, k, v, MDB_PREV))) {
   2951 			ndb_debug("moving back failed: '%s'\n", mdb_strerror(rc));
   2952 			return 0;
   2953 		}
   2954 	}
   2955 
   2956 	return 1;
   2957 }
   2958 
   2959 
   2960 // get some value based on a clustered id key
   2961 int ndb_get_tsid(struct ndb_txn *txn, enum ndb_dbs db, const unsigned char *id,
   2962 		 MDB_val *val)
   2963 {
   2964 	MDB_val k, v;
   2965 	MDB_cursor *cur;
   2966 	int success = 0, rc;
   2967 	struct ndb_tsid tsid;
   2968 
   2969 	// position at the most recent
   2970 	ndb_tsid_high(&tsid, id);
   2971 
   2972 	k.mv_data = &tsid;
   2973 	k.mv_size = sizeof(tsid);
   2974 
   2975 	if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[db], &cur))) {
   2976 		ndb_debug("ndb_get_tsid: failed to open cursor: '%s'\n", mdb_strerror(rc));
   2977 		return 0;
   2978 	}
   2979 
   2980 	if (!ndb_cursor_start(cur, &k, &v))
   2981 		goto cleanup;
   2982 
   2983 	if (memcmp(k.mv_data, id, 32) == 0) {
   2984 		*val = v;
   2985 		success = 1;
   2986 	}
   2987 
   2988 cleanup:
   2989 	mdb_cursor_close(cur);
   2990 	return success;
   2991 }
   2992 
   2993 static void *ndb_lookup_by_key(struct ndb_txn *txn, uint64_t key,
   2994 			       enum ndb_dbs store, size_t *len)
   2995 {
   2996 	MDB_val k, v;
   2997 
   2998 	k.mv_data = &key;
   2999 	k.mv_size = sizeof(key);
   3000 
   3001 	if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[store], &k, &v)) {
   3002 		ndb_debug("ndb_lookup_by_key: mdb_get note failed\n");
   3003 		return NULL;
   3004 	}
   3005 
   3006 	if (len)
   3007 		*len = v.mv_size;
   3008 
   3009 	return v.mv_data;
   3010 }
   3011 
   3012 static void *ndb_lookup_tsid(struct ndb_txn *txn, enum ndb_dbs ind,
   3013 			     enum ndb_dbs store, const unsigned char *pk,
   3014 			     size_t *len, uint64_t *primkey)
   3015 {
   3016 	MDB_val k, v;
   3017 	void *res = NULL;
   3018 	if (len)
   3019 		*len = 0;
   3020 
   3021 	if (!ndb_get_tsid(txn, ind, pk, &k)) {
   3022 		//ndb_debug("ndb_get_profile_by_pubkey: ndb_get_tsid failed\n");
   3023 		return 0;
   3024 	}
   3025 
   3026 	if (primkey)
   3027 		*primkey = *(uint64_t*)k.mv_data;
   3028 
   3029 	if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[store], &k, &v)) {
   3030 		ndb_debug("ndb_get_profile_by_pubkey: mdb_get note failed\n");
   3031 		return 0;
   3032 	}
   3033 
   3034 	res = v.mv_data;
   3035 	assert(((uint64_t)res % 4) == 0);
   3036 	if (len)
   3037 		*len = v.mv_size;
   3038 	return res;
   3039 }
   3040 
   3041 void *ndb_get_profile_by_pubkey(struct ndb_txn *txn, const unsigned char *pk, size_t *len, uint64_t *key)
   3042 {
   3043 	return ndb_lookup_tsid(txn, NDB_DB_PROFILE_PK, NDB_DB_PROFILE, pk, len, key);
   3044 }
   3045 
   3046 struct ndb_note *ndb_get_note_by_id(struct ndb_txn *txn, const unsigned char *id, size_t *len, uint64_t *key)
   3047 {
   3048 	return ndb_lookup_tsid(txn, NDB_DB_NOTE_ID, NDB_DB_NOTE, id, len, key);
   3049 }
   3050 
   3051 static inline uint64_t ndb_get_indexkey_by_id(struct ndb_txn *txn,
   3052 					      enum ndb_dbs db,
   3053 					      const unsigned char *id)
   3054 {
   3055 	MDB_val k;
   3056 
   3057 	if (!ndb_get_tsid(txn, db, id, &k))
   3058 		return 0;
   3059 
   3060 	return *(uint32_t*)k.mv_data;
   3061 }
   3062 
   3063 uint64_t ndb_get_notekey_by_id(struct ndb_txn *txn, const unsigned char *id)
   3064 {
   3065 	return ndb_get_indexkey_by_id(txn, NDB_DB_NOTE_ID, id);
   3066 }
   3067 
   3068 uint64_t ndb_get_profilekey_by_pubkey(struct ndb_txn *txn, const unsigned char *id)
   3069 {
   3070 	return ndb_get_indexkey_by_id(txn, NDB_DB_PROFILE_PK, id);
   3071 }
   3072 
   3073 struct ndb_note *ndb_get_note_by_key(struct ndb_txn *txn, uint64_t key, size_t *len)
   3074 {
   3075 	return ndb_lookup_by_key(txn, key, NDB_DB_NOTE, len);
   3076 }
   3077 
   3078 void *ndb_get_profile_by_key(struct ndb_txn *txn, uint64_t key, size_t *len)
   3079 {
   3080 	return ndb_lookup_by_key(txn, key, NDB_DB_PROFILE, len);
   3081 }
   3082 
   3083 uint64_t
   3084 ndb_read_last_profile_fetch(struct ndb_txn *txn, const unsigned char *pubkey)
   3085 {
   3086 	MDB_val k, v;
   3087 
   3088 	k.mv_data = (unsigned char*)pubkey;
   3089 	k.mv_size = 32;
   3090 
   3091 	if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_LAST_FETCH], &k, &v)) {
   3092 		//ndb_debug("ndb_read_last_profile_fetch: mdb_get note failed\n");
   3093 		return 0;
   3094 	}
   3095 
   3096 	return *((uint64_t*)v.mv_data);
   3097 }
   3098 
   3099 
   3100 static int ndb_has_note(struct ndb_txn *txn, const unsigned char *id)
   3101 {
   3102 	MDB_val val;
   3103 
   3104 	if (!ndb_get_tsid(txn, NDB_DB_NOTE_ID, id, &val))
   3105 		return 0;
   3106 
   3107 	return 1;
   3108 }
   3109 
   3110 static void ndb_txn_from_mdb(struct ndb_txn *txn, struct ndb_lmdb *lmdb,
   3111 			     MDB_txn *mdb_txn)
   3112 {
   3113 	txn->lmdb = lmdb;
   3114 	txn->mdb_txn = mdb_txn;
   3115 }
   3116 
   3117 static enum ndb_idres ndb_ingester_json_controller(void *data, const char *hexid)
   3118 {
   3119 	unsigned char id[32];
   3120 	struct ndb_ingest_controller *c = data;
   3121 	struct ndb_txn txn;
   3122 
   3123 	hex_decode(hexid, 64, id, sizeof(id));
   3124 
   3125 	// let's see if we already have it
   3126 	ndb_txn_from_mdb(&txn, c->lmdb, c->read_txn);
   3127 	c->note = ndb_get_note_by_id(&txn, id, NULL, &c->note_key);
   3128 
   3129 	if (c->note == NULL)
   3130 		return NDB_IDRES_CONT;
   3131 
   3132 	return NDB_IDRES_STOP;
   3133 }
   3134 
   3135 static int ndbprofile_parse_json(flatcc_builder_t *B,
   3136         const char *buf, size_t bufsiz, int flags, NdbProfile_ref_t *profile)
   3137 {
   3138 	flatcc_json_parser_t parser, *ctx = &parser;
   3139 	flatcc_json_parser_init(ctx, B, buf, buf + bufsiz, flags);
   3140 
   3141 	if (flatcc_builder_start_buffer(B, 0, 0, 0))
   3142 		return 0;
   3143 
   3144 	NdbProfile_parse_json_table(ctx, buf, buf + bufsiz, profile);
   3145 	if (ctx->error)
   3146 		return 0;
   3147 
   3148 	if (!flatcc_builder_end_buffer(B, *profile))
   3149 		return 0;
   3150 
   3151 	ctx->end_loc = buf;
   3152 
   3153 
   3154 	return 1;
   3155 }
   3156 
   3157 void ndb_profile_record_builder_init(struct ndb_profile_record_builder *b)
   3158 {
   3159 	b->builder = malloc(sizeof(*b->builder));
   3160 	b->flatbuf = NULL;
   3161 }
   3162 
   3163 void ndb_profile_record_builder_free(struct ndb_profile_record_builder *b)
   3164 {
   3165 	if (b->flatbuf)
   3166 		flatcc_builder_aligned_free(b->flatbuf);
   3167 	if (b->builder) {
   3168 		flatcc_builder_clear(b->builder);
   3169 		free(b->builder);
   3170 	}
   3171 
   3172 	b->builder = NULL;
   3173 	b->flatbuf = NULL;
   3174 
   3175 }
   3176 
   3177 int ndb_process_profile_note(struct ndb_note *note,
   3178 			     struct ndb_profile_record_builder *profile)
   3179 {
   3180 	int res;
   3181 
   3182 	NdbProfile_ref_t profile_table;
   3183 	flatcc_builder_t *builder;
   3184 
   3185 	ndb_profile_record_builder_init(profile);
   3186 	builder = profile->builder;
   3187 	flatcc_builder_init(builder);
   3188 
   3189 	NdbProfileRecord_start_as_root(builder);
   3190 
   3191 	//printf("parsing profile '%.*s'\n", note->content_length, ndb_note_content(note));
   3192 	if (!(res = ndbprofile_parse_json(builder, ndb_note_content(note),
   3193 					  note->content_length,
   3194 					  flatcc_json_parser_f_skip_unknown,
   3195 					  &profile_table)))
   3196 	{
   3197 		ndb_debug("profile_parse_json failed %d '%.*s'\n", res,
   3198 			  note->content_length, ndb_note_content(note));
   3199 		ndb_profile_record_builder_free(profile);
   3200 		return 0;
   3201 	}
   3202 
   3203 	uint64_t received_at = time(NULL);
   3204 	const char *lnurl = "fixme";
   3205 
   3206 	NdbProfileRecord_profile_add(builder, profile_table);
   3207 	NdbProfileRecord_received_at_add(builder, received_at);
   3208 
   3209 	flatcc_builder_ref_t lnurl_off;
   3210 	lnurl_off = flatcc_builder_create_string_str(builder, lnurl);
   3211 
   3212 	NdbProfileRecord_lnurl_add(builder, lnurl_off);
   3213 
   3214 	//*profile = flatcc_builder_finalize_aligned_buffer(builder, profile_len);
   3215 	return 1;
   3216 }
   3217 
   3218 static int ndb_ingester_queue_event(struct ndb_ingester *ingester,
   3219 				    char *json, unsigned len,
   3220 				    unsigned client, const char *relay)
   3221 {
   3222 	struct ndb_ingester_msg msg;
   3223 	msg.type = NDB_INGEST_EVENT;
   3224 
   3225 	msg.event.json = json;
   3226 	msg.event.len = len;
   3227 	msg.event.client = client;
   3228 	msg.event.relay = relay;
   3229 
   3230 	return threadpool_dispatch(&ingester->tp, &msg);
   3231 }
   3232 
   3233 void ndb_ingest_meta_init(struct ndb_ingest_meta *meta, unsigned client, const char *relay)
   3234 {
   3235 	meta->client = client;
   3236 	meta->relay = relay;
   3237 }
   3238 
   3239 static int ndb_ingest_event(struct ndb_ingester *ingester, const char *json,
   3240 			    int len, struct ndb_ingest_meta *meta)
   3241 {
   3242 	const char *relay = meta->relay;
   3243 
   3244 	// Without this, we get bus errors in the json parser inside when
   3245 	// trying to ingest empty kind 6 reposts... we should probably do fuzz
   3246 	// testing on inputs to the json parser
   3247 	if (len == 0)
   3248 		return 0;
   3249 
   3250 	// Since we need to return as soon as possible, and we're not
   3251 	// making any assumptions about the lifetime of the string, we
   3252 	// definitely need to copy the json here. In the future once we
   3253 	// have our thread that manages a websocket connection, we can
   3254 	// avoid the copy and just use the buffer we get from that
   3255 	// thread.
   3256 	char *json_copy = strdupn(json, len);
   3257 	if (json_copy == NULL)
   3258 		return 0;
   3259 
   3260 	if (relay != NULL) {
   3261 		relay = strdup(meta->relay);
   3262 		if (relay == NULL)
   3263 			return 0;
   3264 	}
   3265 
   3266 	return ndb_ingester_queue_event(ingester, json_copy, len, meta->client, relay);
   3267 }
   3268 
   3269 
   3270 static int ndb_ingester_process_note(secp256k1_context *ctx,
   3271 				     struct ndb_note *note,
   3272 				     size_t note_size,
   3273 				     struct ndb_writer_msg *out,
   3274 				     struct ndb_ingester *ingester,
   3275 				     unsigned char *scratch,
   3276 				     const char *relay)
   3277 {
   3278 	enum ndb_ingest_filter_action action;
   3279 	struct ndb_ingest_meta meta;
   3280 
   3281 	action = NDB_INGEST_ACCEPT;
   3282 
   3283 	if (ingester->filter)
   3284 		action = ingester->filter(ingester->filter_context, note);
   3285 
   3286 	if (action == NDB_INGEST_REJECT)
   3287 		return 0;
   3288 
   3289 	// some special situations we might want to skip sig validation,
   3290 	// like during large imports
   3291 	if (action == NDB_INGEST_SKIP_VALIDATION || (ingester->flags & NDB_FLAG_SKIP_NOTE_VERIFY)) {
   3292 		// if we're skipping validation we don't need to verify
   3293 	} else {
   3294 		// verify! If it's an invalid note we don't need to
   3295 		// bother writing it to the database
   3296 		if (!ndb_note_verify(ctx, scratch, ingester->scratch_size, note)) {
   3297 			ndb_debug("note verification failed\n");
   3298 			return 0;
   3299 		}
   3300 	}
   3301 
   3302 	// we didn't find anything. let's send it
   3303 	// to the writer thread
   3304 	note = realloc(note, note_size);
   3305 	assert(((uint64_t)note % 4) == 0);
   3306 
   3307 	if (note->kind == 0) {
   3308 		struct ndb_profile_record_builder *b =
   3309 			&out->profile.record;
   3310 
   3311 		ndb_process_profile_note(note, b);
   3312 
   3313 		out->type = NDB_WRITER_PROFILE;
   3314 		ndb_writer_note_init(&out->profile.note, note, note_size, relay);
   3315 		return 1;
   3316 	} else if (note->kind == 6) {
   3317 		// process the repost if we have a repost event
   3318 		ndb_debug("processing kind 6 repost\n");
   3319 		// dup the relay string
   3320 		ndb_ingest_meta_init(&meta, 0, relay);
   3321 		ndb_ingest_event(ingester, ndb_note_content(note),
   3322 					   ndb_note_content_length(note),
   3323 					   &meta);
   3324 	}
   3325 
   3326 	out->type = NDB_WRITER_NOTE;
   3327 	ndb_writer_note_init(&out->note, note, note_size, relay);
   3328 
   3329 	return 1;
   3330 }
   3331 
   3332 int ndb_note_seen_on_relay(struct ndb_txn *txn, uint64_t note_key, const char *relay)
   3333 {
   3334 	MDB_val k, v;
   3335 	MDB_cursor *cur;
   3336 	int rc, len;
   3337 	char relay_buf[256];
   3338 
   3339 	if (relay == NULL)
   3340 		return 0;
   3341 
   3342 	len = strlen(relay);
   3343 
   3344 	if (!(len = prepare_relay_buf(relay_buf, sizeof(relay_buf), relay, len)))
   3345 		return 0;
   3346 
   3347 	assert((len % 8) == 0);
   3348 
   3349 	k.mv_data = &note_key;
   3350 	k.mv_size = sizeof(note_key);
   3351 
   3352 	v.mv_data = relay_buf;
   3353 	v.mv_size = len;
   3354 
   3355 	if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAYS], &cur)) != MDB_SUCCESS)
   3356 		return 0;
   3357 
   3358 	rc = mdb_cursor_get(cur, &k, &v, MDB_GET_BOTH);
   3359 	ndb_debug("seen_on_relay result: %s\n", mdb_strerror(rc));
   3360 	mdb_cursor_close(cur);
   3361 
   3362 	return rc == MDB_SUCCESS;
   3363 }
   3364 
   3365 // process the relay for the note. this is called when we already have the
   3366 // note in the database but still need to check if the relay needs to be
   3367 // written to the relay indexes for corresponding note
   3368 static int ndb_process_note_relay(struct ndb_txn *txn, struct ndb_writer_msg *out,
   3369 				  uint64_t note_key, struct ndb_note *note,
   3370 				  const char *relay)
   3371 {
   3372 	// query to see if we already have the relay on this note
   3373 	if (ndb_note_seen_on_relay(txn, note_key, relay)) {
   3374 		return 0;
   3375 	}
   3376 
   3377 	// if not, tell the writer thread to emit a NOTE_RELAY event
   3378 	out->type = NDB_WRITER_NOTE_RELAY;
   3379 
   3380 	ndb_debug("pushing NDB_WRITER_NOTE_RELAY with note_key %" PRIu64 "\n", note_key);
   3381 	out->note_relay.relay = relay;
   3382 	out->note_relay.note_key = note_key;
   3383 	out->note_relay.kind = ndb_note_kind(note);
   3384 	out->note_relay.created_at = ndb_note_created_at(note);
   3385 
   3386 	return 1;
   3387 }
   3388 
   3389 static int ndb_ingester_process_event(secp256k1_context *ctx,
   3390 				      struct ndb_ingester *ingester,
   3391 				      struct ndb_ingester_event *ev,
   3392 				      struct ndb_writer_msg *out,
   3393 				      unsigned char *scratch,
   3394 				      MDB_txn *read_txn)
   3395 {
   3396 	struct ndb_tce tce;
   3397 	struct ndb_fce fce;
   3398 	struct ndb_note *note;
   3399 	struct ndb_ingest_controller controller;
   3400 	struct ndb_id_cb cb;
   3401 	void *buf;
   3402 	int ok;
   3403 	size_t bufsize, note_size;
   3404 
   3405 	ok = 0;
   3406 
   3407 	// we will use this to check if we already have it in the DB during
   3408 	// ID parsing
   3409 	controller.read_txn = read_txn;
   3410 	controller.lmdb = ingester->lmdb;
   3411 	cb.fn = ndb_ingester_json_controller;
   3412 	cb.data = &controller;
   3413 
   3414 	// since we're going to be passing this allocated note to a different
   3415 	// thread, we can't use thread-local buffers. just allocate a block
   3416         bufsize = max(ev->len * 8.0, 4096);
   3417 	buf = malloc(bufsize);
   3418 	if (!buf) {
   3419 		ndb_debug("couldn't malloc buf\n");
   3420 		return 0;
   3421 	}
   3422 
   3423 	note_size =
   3424 		ev->client ?
   3425 		ndb_client_event_from_json(ev->json, ev->len, &fce, buf, bufsize, &cb) :
   3426 		ndb_ws_event_from_json(ev->json, ev->len, &tce, buf, bufsize, &cb);
   3427 
   3428 	// This is a result from our special json parser. It parsed the id
   3429 	// and found that we already have it in the database
   3430 	if ((int)note_size == -42) {
   3431 		assert(controller.note != NULL);
   3432 		assert(controller.note_key != 0);
   3433 		struct ndb_txn txn;
   3434 		ndb_txn_from_mdb(&txn, ingester->lmdb, read_txn);
   3435 
   3436 		// we still need to process the relays on the note even
   3437 		// if we already have it
   3438 	 	if (ev->relay && ndb_process_note_relay(&txn, out,
   3439 							controller.note_key,
   3440 							controller.note,
   3441 							ev->relay))
   3442 		{
   3443 			// free note buf here since we don't pass the note to the writer thread
   3444 			free(buf);
   3445 			goto success;
   3446 		} else {
   3447 			// we already have the note and there are no new
   3448 			// relays to process. nothing to write.
   3449 			goto cleanup;
   3450 		}
   3451 	} else if (note_size == 0) {
   3452 		ndb_debug("failed to parse '%.*s'\n", ev->len, ev->json);
   3453 		goto cleanup;
   3454 	}
   3455 
   3456 	//ndb_debug("parsed evtype:%d '%.*s'\n", tce.evtype, ev->len, ev->json);
   3457 
   3458 	if (ev->client) {
   3459 		switch (fce.evtype) {
   3460 		case NDB_FCE_EVENT:
   3461 			note = fce.event.note;
   3462 			if (note != buf) {
   3463 				ndb_debug("note buffer not equal to malloc'd buffer\n");
   3464 				goto cleanup;
   3465 			}
   3466 
   3467 			if (!ndb_ingester_process_note(ctx, note, note_size,
   3468 						       out, ingester, scratch,
   3469 						       ev->relay)) {
   3470 				ndb_debug("failed to process note\n");
   3471 				goto cleanup;
   3472 			} else {
   3473 				goto success;
   3474 			}
   3475 		}
   3476 	} else {
   3477 		switch (tce.evtype) {
   3478 		case NDB_TCE_AUTH:   goto cleanup;
   3479 		case NDB_TCE_NOTICE: goto cleanup;
   3480 		case NDB_TCE_EOSE:   goto cleanup;
   3481 		case NDB_TCE_OK:     goto cleanup;
   3482 		case NDB_TCE_EVENT:
   3483 			note = tce.event.note;
   3484 			if (note != buf) {
   3485 				ndb_debug("note buffer not equal to malloc'd buffer\n");
   3486 				goto cleanup;
   3487 			}
   3488 
   3489 			if (!ndb_ingester_process_note(ctx, note, note_size,
   3490 						       out, ingester, scratch,
   3491 						       ev->relay)) {
   3492 				ndb_debug("failed to process note\n");
   3493 				goto cleanup;
   3494 			} else {
   3495 				goto success;
   3496 			}
   3497 		}
   3498 	}
   3499 
   3500 
   3501 success:
   3502 	free(ev->json);
   3503 	// we don't free relay or buf since those are passed to the writer thread
   3504 	return 1;
   3505 
   3506 cleanup:
   3507 	free(ev->json);
   3508 	if (ev->relay)
   3509 		free((void*)ev->relay);
   3510 	free(buf);
   3511 
   3512 	return ok;
   3513 }
   3514 
   3515 static uint64_t ndb_get_last_key(MDB_txn *txn, MDB_dbi db)
   3516 {
   3517 	MDB_cursor *mc;
   3518 	MDB_val key, val;
   3519 
   3520 	if (mdb_cursor_open(txn, db, &mc))
   3521 		return 0;
   3522 
   3523 	if (mdb_cursor_get(mc, &key, &val, MDB_LAST)) {
   3524 		mdb_cursor_close(mc);
   3525 		return 0;
   3526 	}
   3527 
   3528 	mdb_cursor_close(mc);
   3529 
   3530 	assert(key.mv_size == 8);
   3531         return *((uint64_t*)key.mv_data);
   3532 }
   3533 
   3534 //
   3535 // make a search key meant for user queries without any other note info
   3536 static void ndb_make_search_key_low(struct ndb_search_key *key, const char *search)
   3537 {
   3538 	memset(key->id, 0, sizeof(key->id));
   3539 	key->timestamp = 0;
   3540 	lowercase_strncpy(key->search, search, sizeof(key->search) - 1);
   3541 	key->search[sizeof(key->search) - 1] = '\0';
   3542 }
   3543 
   3544 int ndb_search_profile(struct ndb_txn *txn, struct ndb_search *search, const char *query)
   3545 {
   3546 	int rc;
   3547 	struct ndb_search_key s;
   3548 	MDB_val k, v;
   3549 	search->cursor = NULL;
   3550 
   3551 	MDB_cursor **cursor = (MDB_cursor **)&search->cursor;
   3552 
   3553 	ndb_make_search_key_low(&s, query);
   3554 
   3555 	k.mv_data = &s;
   3556 	k.mv_size = sizeof(s);
   3557 
   3558 	if ((rc = mdb_cursor_open(txn->mdb_txn,
   3559 				  txn->lmdb->dbs[NDB_DB_PROFILE_SEARCH],
   3560 				  cursor))) {
   3561 		printf("search_profile: cursor opened failed: %s\n",
   3562 				mdb_strerror(rc));
   3563 		return 0;
   3564 	}
   3565 
   3566 	// Position cursor at the next key greater than or equal to the specified key
   3567 	if (mdb_cursor_get(search->cursor, &k, &v, MDB_SET_RANGE)) {
   3568 		printf("search_profile: cursor get failed\n");
   3569 		goto cleanup;
   3570 	} else {
   3571 		search->key = k.mv_data;
   3572 		assert(v.mv_size == 8);
   3573 		search->profile_key = *((uint64_t*)v.mv_data);
   3574 		return 1;
   3575 	}
   3576 
   3577 cleanup:
   3578 	mdb_cursor_close(search->cursor);
   3579 	search->cursor = NULL;
   3580 	return 0;
   3581 }
   3582 
   3583 void ndb_search_profile_end(struct ndb_search *search)
   3584 {
   3585 	if (search->cursor)
   3586 		mdb_cursor_close(search->cursor);
   3587 }
   3588 
   3589 int ndb_search_profile_next(struct ndb_search *search)
   3590 {
   3591 	int rc;
   3592 	MDB_val k, v;
   3593 	unsigned char *init_id;
   3594 
   3595 	init_id = search->key->id;
   3596 	k.mv_data = search->key;
   3597 	k.mv_size = sizeof(*search->key);
   3598 
   3599 retry:
   3600 	if ((rc = mdb_cursor_get(search->cursor, &k, &v, MDB_NEXT))) {
   3601 		ndb_debug("ndb_search_profile_next: %s\n",
   3602 				mdb_strerror(rc));
   3603 		return 0;
   3604 	} else {
   3605 		search->key = k.mv_data;
   3606 		assert(v.mv_size == 8);
   3607 		search->profile_key = *((uint64_t*)v.mv_data);
   3608 
   3609 		// skip duplicate pubkeys
   3610 		if (!memcmp(init_id, search->key->id, 32))
   3611 			goto retry;
   3612 	}
   3613 
   3614 	return 1;
   3615 }
   3616 
   3617 //
   3618 // The relay kind index has a layout like so (so we don't need dupsort)
   3619 //
   3620 // - note_id:        00 + 8 bytes
   3621 // - kind:           08 + 8 bytes
   3622 // - created_at:     16 + 8 bytes
   3623 // - relay_url_size: 24 + 1 byte
   3624 // - relay_url:      25 + n byte null-terminated string
   3625 // - pad to 8 byte alignment
   3626 //
   3627 // The key sort order is:
   3628 //
   3629 // relay_url, kind, created_at
   3630 //
   3631 static int ndb_relay_kind_cmp(const MDB_val *a, const MDB_val *b)
   3632 {
   3633 	int cmp;
   3634 	MDB_val va, vb;
   3635 	uint64_t iva, ivb;
   3636 	unsigned char *ad = (unsigned char *)a->mv_data;
   3637 	unsigned char *bd = (unsigned char *)b->mv_data;
   3638 	assert(((uint64_t)a->mv_data % 8) == 0);
   3639 
   3640 	va.mv_size = *(ad + 24);
   3641 	va.mv_data =   ad + 25;
   3642 
   3643 	vb.mv_size = *(bd + 24);
   3644 	vb.mv_data =   bd + 25;
   3645 
   3646 	cmp = mdb_cmp_memn(&va, &vb);
   3647 	if (cmp) return cmp;
   3648 
   3649 	// kind
   3650 	iva = *(uint64_t*)(ad + 8);
   3651 	ivb = *(uint64_t*)(bd + 8);
   3652 
   3653 	if (iva < ivb)
   3654 		return -1;
   3655 	else if (iva > ivb)
   3656 		return 1;
   3657 
   3658 	// created_at
   3659 	iva = *(uint64_t*)(ad + 16);
   3660 	ivb = *(uint64_t*)(bd + 16);
   3661 
   3662 	if (iva < ivb)
   3663 		return -1;
   3664 	else if (iva > ivb)
   3665 		return 1;
   3666 
   3667 	// note_id (so we don't need dupsort logic)
   3668 	iva = *(uint64_t*)ad;
   3669 	ivb = *(uint64_t*)bd;
   3670 
   3671 	if (iva < ivb)
   3672 		return -1;
   3673 	else if (iva > ivb)
   3674 		return 1;
   3675 
   3676 	return 0;
   3677 }
   3678 
   3679 static int ndb_search_key_cmp(const MDB_val *a, const MDB_val *b)
   3680 {
   3681 	int cmp;
   3682 	struct ndb_search_key *ska, *skb;
   3683 
   3684 	ska = a->mv_data;
   3685 	skb = b->mv_data;
   3686 
   3687 	MDB_val a2 = *a;
   3688 	MDB_val b2 = *b;
   3689 
   3690 	a2.mv_data = ska->search;
   3691 	a2.mv_size = sizeof(ska->search) + sizeof(ska->id);
   3692 
   3693 	cmp = mdb_cmp_memn(&a2, &b2);
   3694 	if (cmp) return cmp;
   3695 
   3696 	if (ska->timestamp < skb->timestamp)
   3697 		return -1;
   3698 	else if (ska->timestamp > skb->timestamp)
   3699 		return 1;
   3700 
   3701 	return 0;
   3702 }
   3703 
   3704 static int ndb_write_profile_pk_index(struct ndb_txn *txn, struct ndb_note *note, uint64_t profile_key)
   3705 
   3706 {
   3707 	MDB_val key, val;
   3708 	int rc;
   3709 	struct ndb_tsid tsid;
   3710 	MDB_dbi pk_db;
   3711 
   3712 	pk_db = txn->lmdb->dbs[NDB_DB_PROFILE_PK];
   3713 
   3714 	// write profile_pk + created_at index
   3715 	ndb_tsid_init(&tsid, note->pubkey, note->created_at);
   3716 
   3717 	key.mv_data = &tsid;
   3718 	key.mv_size = sizeof(tsid);
   3719 	val.mv_data = &profile_key;
   3720 	val.mv_size = sizeof(profile_key);
   3721 
   3722 	if ((rc = mdb_put(txn->mdb_txn, pk_db, &key, &val, 0))) {
   3723 		ndb_debug("write profile_pk(%" PRIu64 ") to db failed: %s\n",
   3724 				profile_key, mdb_strerror(rc));
   3725 		return 0;
   3726 	}
   3727 
   3728 	return 1;
   3729 }
   3730 
   3731 static int ndb_write_profile(struct ndb_txn *txn,
   3732 			     struct ndb_writer_profile *profile,
   3733 			     uint64_t note_key)
   3734 {
   3735 	uint64_t profile_key;
   3736 	struct ndb_note *note;
   3737 	void *flatbuf;
   3738 	size_t flatbuf_len;
   3739 	int rc;
   3740 
   3741 	MDB_val key, val;
   3742 	MDB_dbi profile_db;
   3743 
   3744 	note = profile->note.note;
   3745 
   3746 	// add note_key to profile record
   3747 	NdbProfileRecord_note_key_add(profile->record.builder, note_key);
   3748 	NdbProfileRecord_end_as_root(profile->record.builder);
   3749 
   3750 	flatbuf = profile->record.flatbuf =
   3751 		flatcc_builder_finalize_aligned_buffer(profile->record.builder, &flatbuf_len);
   3752 
   3753 	assert(((uint64_t)flatbuf % 8) == 0);
   3754 
   3755 	// TODO: this may not be safe!?
   3756 	flatbuf_len = (flatbuf_len + 7) & ~7;
   3757 
   3758 	//assert(NdbProfileRecord_verify_as_root(flatbuf, flatbuf_len) == 0);
   3759 
   3760 	// get dbs
   3761 	profile_db = txn->lmdb->dbs[NDB_DB_PROFILE];
   3762 
   3763 	// get new key
   3764 	profile_key = ndb_get_last_key(txn->mdb_txn, profile_db) + 1;
   3765 
   3766 	// write profile to profile store
   3767 	key.mv_data = &profile_key;
   3768 	key.mv_size = sizeof(profile_key);
   3769 	val.mv_data = flatbuf;
   3770 	val.mv_size = flatbuf_len;
   3771 
   3772 	if ((rc = mdb_put(txn->mdb_txn, profile_db, &key, &val, 0))) {
   3773 		ndb_debug("write profile to db failed: %s\n", mdb_strerror(rc));
   3774 		return 0;
   3775 	}
   3776 
   3777 	// write last fetched record
   3778 	if (!ndb_maybe_write_last_profile_fetch(txn, note)) {
   3779 		ndb_debug("failed to write last profile fetched record\n");
   3780 	}
   3781 
   3782 	// write profile pubkey index
   3783 	if (!ndb_write_profile_pk_index(txn, note, profile_key)) {
   3784 		ndb_debug("failed to write profile pubkey index\n");
   3785 		return 0;
   3786 	}
   3787 
   3788 	// write name, display_name profile search indices
   3789 	if (!ndb_write_profile_search_indices(txn, note, profile_key,
   3790 					      flatbuf)) {
   3791 		ndb_debug("failed to write profile search indices\n");
   3792 		return 0;
   3793 	}
   3794 
   3795 	return 1;
   3796 }
   3797 
   3798 int ndb_set_note_meta(struct ndb *ndb, const unsigned char *id, struct ndb_note_meta *meta)
   3799 {
   3800 	struct ndb_writer_msg msg;
   3801 	struct ndb_writer_note_meta *meta_msg = &msg.note_meta;
   3802 
   3803 	msg.type = NDB_WRITER_NOTE_META;
   3804 
   3805 	memcpy(meta_msg->note_id, id, 32);
   3806 	meta_msg->metadata = meta;
   3807 
   3808 	return ndb_writer_queue_msg(&ndb->writer, &msg);
   3809 }
   3810 
   3811 int ndb_writer_set_note_meta(struct ndb_txn *txn, const unsigned char *id, struct ndb_note_meta *meta)
   3812 {
   3813 	int rc;
   3814 	MDB_val k, v;
   3815 	MDB_dbi note_meta_db;
   3816 
   3817 	// get dbs
   3818 	note_meta_db = txn->lmdb->dbs[NDB_DB_META];
   3819 
   3820 	k.mv_data = (unsigned char *)id;
   3821 	k.mv_size = 32;
   3822 
   3823 	v.mv_data = (unsigned char *)meta;
   3824 	v.mv_size = ndb_note_meta_total_size(meta);
   3825 
   3826 	if ((rc = mdb_put(txn->mdb_txn, note_meta_db, &k, &v, 0))) {
   3827 		ndb_debug("ndb_set_note_meta: write note metadata to db failed: %s\n", mdb_strerror(rc));
   3828 		return 0;
   3829 	}
   3830 
   3831 	return 1;
   3832 }
   3833 
   3834 struct ndb_note_meta *ndb_get_note_meta(struct ndb_txn *txn, const unsigned char *id)
   3835 {
   3836 	MDB_val k, v;
   3837 
   3838 	k.mv_data = (unsigned char*)id;
   3839 	k.mv_size = 32;
   3840 
   3841 	if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &k, &v)) {
   3842 		ndb_debug("ndb_get_note_meta: mdb_get note failed\n");
   3843 		return NULL;
   3844 	}
   3845 
   3846 	return v.mv_data;
   3847 }
   3848 
   3849 /* write reaction stats if its a valid reaction */
   3850 static int ndb_process_reaction(
   3851 		struct ndb_txn *txn,
   3852 		struct ndb_note *note,
   3853 		unsigned char **liked,
   3854 		unsigned char *scratch,
   3855 		size_t scratch_size)
   3856 {
   3857 	const char *content;
   3858 	int rc;
   3859 	uint32_t *count;
   3860 	MDB_val key, val;
   3861 	union ndb_reaction_str reaction_str;
   3862 	struct ndb_note_meta *meta;
   3863 	struct ndb_note_meta_entry *entry;
   3864 	enum ndb_meta_clone_result cres;
   3865 	char strbuf[128];
   3866 
   3867 	*liked = ndb_note_last_id_tag(note, 'e');
   3868 
   3869 	if (*liked == NULL)
   3870 		return 0;
   3871 
   3872 	meta = ndb_get_note_meta(txn, *liked);
   3873 
   3874 	/* initial builder setup, build reaction string from reaction contents */
   3875 	content = ndb_note_content(note);
   3876 	if (!ndb_reaction_set(&reaction_str, content)) {
   3877 		ndb_debug("reaction string '%s' was too big\n", content);
   3878 		/* string was too big, let's just record a `+` for now */
   3879 		rc = ndb_reaction_set(&reaction_str, "+");
   3880 		assert(rc);
   3881 	}
   3882 
   3883 	cres = ndb_note_meta_clone_with_entry(&meta,
   3884 			&entry,
   3885 			NDB_NOTE_META_REACTION,
   3886 			&reaction_str.binmoji,
   3887 			scratch,
   3888 			scratch_size);
   3889 
   3890 	switch (cres) {
   3891 	case NDB_META_CLONE_FAILED:
   3892 		return 0;
   3893 	case NDB_META_CLONE_NEW_ENTRY:
   3894 		ndb_reaction_to_str(&reaction_str, strbuf);
   3895 		/* printf("initializing reaction stats for %s\n", strbuf); */
   3896 		ndb_note_meta_reaction_set(entry, 1, reaction_str);
   3897 		break;
   3898 	case NDB_META_CLONE_EXISTING_ENTRY:
   3899 		count = ndb_note_meta_reaction_count(entry);
   3900 		/* printf("increasing count from %d to %d\n", (int)*count, (int)*count+1); */
   3901 		(*count)++;
   3902 		break;
   3903 	}
   3904 
   3905 	key.mv_data = *liked;
   3906 	key.mv_size = 32;
   3907 
   3908 	val.mv_data = meta;
   3909 	val.mv_size = ndb_note_meta_total_size(meta);
   3910 	assert((val.mv_size % 8) == 0);
   3911 
   3912 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
   3913 		ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
   3914 		return 0;
   3915 	}
   3916 
   3917 	return 1;
   3918 }
   3919 
   3920 static int ndb_increment_total_reactions(
   3921 		struct ndb_txn *txn,
   3922 		unsigned char *liked,
   3923 		unsigned char *scratch,
   3924 		size_t scratch_size)
   3925 {
   3926 	MDB_val key, val;
   3927 	uint32_t *total_reactions;
   3928 	struct ndb_note_meta *meta;
   3929 	struct ndb_note_meta_entry *entry;
   3930 	int rc;
   3931 
   3932 	meta = ndb_get_note_meta(txn, liked);
   3933 	rc = ndb_note_meta_clone_with_entry(&meta, &entry,
   3934 		NDB_NOTE_META_COUNTS,
   3935 		NULL, /* payload to match. only relevant for reactions */
   3936 		scratch,
   3937 		scratch_size);
   3938 
   3939 	switch (rc) {
   3940 	case NDB_META_CLONE_FAILED:
   3941 		return 0;
   3942 	case NDB_META_CLONE_NEW_ENTRY:
   3943 		ndb_note_meta_counts_set(entry, 1, 0, 0, 0, 0);
   3944 		break;
   3945 	case NDB_META_CLONE_EXISTING_ENTRY:
   3946 		total_reactions = ndb_note_meta_counts_total_reactions(entry);
   3947 		(*total_reactions)++;
   3948 		break;
   3949 	}
   3950 
   3951 	key.mv_data = liked;
   3952 	key.mv_size = 32;
   3953 
   3954 	val.mv_data = meta;
   3955 	val.mv_size = ndb_note_meta_total_size(meta);
   3956 	assert((val.mv_size % 8) == 0);
   3957 
   3958 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
   3959 		ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
   3960 		return 0;
   3961 	}
   3962 
   3963 	return 1;
   3964 }
   3965 
   3966 
   3967 // When receiving a reaction note, look for the liked id and increase the
   3968 // reaction counter in the note metadata database
   3969 static int ndb_write_reaction_stats(struct ndb_txn *txn, struct ndb_note *note,
   3970 				    unsigned char *scratch,
   3971 				    size_t scratch_size)
   3972 {
   3973 	unsigned char *liked;
   3974 	/* we short circuit here since we only want to increment total reaction count
   3975 	 * if its a valid reaction */
   3976 	return ndb_process_reaction(txn, note, &liked, scratch, scratch_size) &&
   3977 	       ndb_increment_total_reactions(txn, liked, scratch, scratch_size);
   3978 }
   3979 
   3980 
   3981 static int ndb_write_note_id_index(struct ndb_txn *txn, struct ndb_note *note,
   3982 				   uint64_t note_key)
   3983 
   3984 {
   3985 	struct ndb_tsid tsid;
   3986 	int rc;
   3987 	MDB_val key, val;
   3988 	MDB_dbi id_db;
   3989 
   3990 	ndb_tsid_init(&tsid, note->id, note->created_at);
   3991 
   3992 	key.mv_data = &tsid;
   3993 	key.mv_size = sizeof(tsid);
   3994 	val.mv_data = &note_key;
   3995 	val.mv_size = sizeof(note_key);
   3996 
   3997 	id_db = txn->lmdb->dbs[NDB_DB_NOTE_ID];
   3998 
   3999 	if ((rc = mdb_put(txn->mdb_txn, id_db, &key, &val, 0))) {
   4000 		ndb_debug("write note id index to db failed: %s\n",
   4001 				mdb_strerror(rc));
   4002 		return 0;
   4003 	}
   4004 
   4005 	return 1;
   4006 }
   4007 
   4008 static int ndb_filter_group_add_filters(struct ndb_filter_group *group,
   4009 					struct ndb_filter *filters,
   4010 					int num_filters)
   4011 {
   4012 	int i;
   4013 
   4014 	for (i = 0; i < num_filters; i++) {
   4015 		if (!ndb_filter_group_add(group, &filters[i]))
   4016 			return 0;
   4017 	}
   4018 
   4019 	return 1;
   4020 }
   4021 
   4022 
   4023 static struct ndb_filter_elements *
   4024 ndb_filter_find_elements(struct ndb_filter *filter, enum ndb_filter_fieldtype typ)
   4025 {
   4026 	int i;
   4027 	struct ndb_filter_elements *els;
   4028 
   4029 	for (i = 0; i < filter->num_elements; i++) {
   4030 		els = ndb_filter_get_elements(filter, i);
   4031 		assert(els);
   4032 		if (els->field.type == typ) {
   4033 			return els;
   4034 		}
   4035 	}
   4036 
   4037 	return NULL;
   4038 }
   4039 
   4040 static const char *ndb_filter_find_search(struct ndb_filter *filter)
   4041 {
   4042 	struct ndb_filter_elements *els;
   4043 
   4044 	if (!(els = ndb_filter_find_elements(filter, NDB_FILTER_SEARCH)))
   4045 		return NULL;
   4046 
   4047 	return ndb_filter_get_string_element(filter, els, 0);
   4048 }
   4049 
   4050 int ndb_filter_is_subset_of(const struct ndb_filter *a, const struct ndb_filter *b)
   4051 {
   4052 	int i;
   4053 	struct ndb_filter_elements *b_field, *a_field;
   4054 
   4055         // Everything is always a subset of {}
   4056 	if (b->num_elements == 0)
   4057 		return 1;
   4058 
   4059         // We can't be a subset if the number of elements in the other
   4060         // filter is larger then the number of elements we have.
   4061 	if (b->num_elements > a->num_elements)
   4062 		return 0;
   4063 
   4064         // If our filter count matches, we can only be a subset if we are
   4065         // equal
   4066 	if (b->num_elements == a->num_elements)
   4067 		return ndb_filter_eq(a, b);
   4068 
   4069         // If our element count is larger than the other filter, then we
   4070         // must see if every element in the other filter exists in ours. If
   4071         // so, then we are a subset of the other.
   4072         //
   4073         // eg: B={k:1, a:b} <- A={t:x, k:1, a:b}
   4074         //
   4075         // A is a subset of B because `k:1` and `a:b` both exist in A
   4076 
   4077 	for (i = 0; i < b->num_elements; i++) {
   4078 		b_field = ndb_filter_get_elements((struct ndb_filter*)b, i);
   4079 		a_field = ndb_filter_find_elements((struct ndb_filter*)a,
   4080 						   b_field->field.type);
   4081 
   4082 		if (a_field == NULL)
   4083 			return 0;
   4084 
   4085 		if (!ndb_filter_field_eq((struct ndb_filter*)a, a_field,
   4086 					 (struct ndb_filter*)b, b_field))
   4087 			return 0;
   4088 	}
   4089 
   4090 	return 1;
   4091 }
   4092 
   4093 int ndb_filter_eq(const struct ndb_filter *a, const struct ndb_filter *b)
   4094 {
   4095 	int i;
   4096 	struct ndb_filter_elements *a_els, *b_els;
   4097 
   4098 	if (a->num_elements != b->num_elements)
   4099 		return 0;
   4100 
   4101 	for (i = 0; i < a->num_elements; i++) {
   4102 		a_els = ndb_filter_get_elements((struct ndb_filter*)a, i);
   4103 		b_els = ndb_filter_find_elements((struct ndb_filter *)b,
   4104 						 a_els->field.type);
   4105 
   4106 		if (b_els == NULL)
   4107 			return 0;
   4108 
   4109 		if (!ndb_filter_field_eq((struct ndb_filter*)a, a_els,
   4110 					 (struct ndb_filter*)b, b_els))
   4111 			return 0;
   4112 	}
   4113 
   4114 	return 1;
   4115 }
   4116 
   4117 
   4118 static uint64_t *
   4119 ndb_filter_get_elem(struct ndb_filter *filter, enum ndb_filter_fieldtype typ)
   4120 {
   4121 	struct ndb_filter_elements *els;
   4122 	if ((els = ndb_filter_find_elements(filter, typ)))
   4123 		return &els->elements[0];
   4124 	return NULL;
   4125 }
   4126 
   4127 static uint64_t *ndb_filter_get_int(struct ndb_filter *filter,
   4128 				    enum ndb_filter_fieldtype typ)
   4129 {
   4130 	uint64_t *el;
   4131 	if (NULL == (el = ndb_filter_get_elem(filter, typ)))
   4132 		return NULL;
   4133 	return el;
   4134 }
   4135 
   4136 static inline int push_query_result(struct ndb_query_results *results,
   4137 				    struct ndb_query_result *result)
   4138 {
   4139 	return cursor_push(&results->cur, (unsigned char*)result, sizeof(*result));
   4140 }
   4141 
   4142 static int compare_query_results(const void *pa, const void *pb)
   4143 {
   4144 	struct ndb_query_result *a, *b;
   4145 
   4146 	a = (struct ndb_query_result *)pa;
   4147 	b = (struct ndb_query_result *)pb;
   4148 
   4149 	if (a->note->created_at == b->note->created_at) {
   4150 		return 0;
   4151 	} else if (a->note->created_at > b->note->created_at) {
   4152 		return -1;
   4153 	} else {
   4154 		return 1;
   4155 	}
   4156 }
   4157 
   4158 static void ndb_query_result_init(struct ndb_query_result *res,
   4159 				  struct ndb_note *note,
   4160 				  uint64_t note_size,
   4161 				  uint64_t note_id)
   4162 {
   4163 	*res = (struct ndb_query_result){
   4164 		.note_id = note_id,
   4165 		.note_size = note_size,
   4166 		.note = note,
   4167 	};
   4168 }
   4169 
   4170 static int query_is_full(struct ndb_query_results *results, int limit)
   4171 {
   4172 	if (results->cur.p >= results->cur.end)
   4173 		return 1;
   4174 
   4175 	return cursor_count(&results->cur, sizeof(struct ndb_query_result)) >= limit;
   4176 }
   4177 
   4178 static int ndb_query_plan_execute_search(struct ndb_txn *txn,
   4179 					 struct ndb_filter *filter,
   4180 					 struct ndb_query_results *results,
   4181 					 int limit)
   4182 {
   4183 	const char *search;
   4184 	int i;
   4185 	struct ndb_text_search_results text_results;
   4186 	struct ndb_text_search_result *text_result;
   4187 	struct ndb_text_search_config config;
   4188 	struct ndb_query_result result;
   4189 
   4190 	ndb_default_text_search_config(&config);
   4191 
   4192 	if (!(search = ndb_filter_find_search(filter)))
   4193 		return 0;
   4194 
   4195 	if (!ndb_text_search_with(txn, search, &text_results, &config, filter))
   4196 		return 0;
   4197 
   4198 	for (i = 0; i < text_results.num_results; i++) {
   4199 		if (query_is_full(results, limit))
   4200 			break;
   4201 
   4202 		text_result = &text_results.results[i];
   4203 
   4204 		result.note = text_result->note;
   4205 		result.note_size = text_result->note_size;
   4206 		result.note_id = text_result->key.note_id;
   4207 
   4208 		if (!push_query_result(results, &result))
   4209 			break;
   4210 	}
   4211 
   4212 	return 1;
   4213 }
   4214 
   4215 static int ndb_query_plan_execute_ids(struct ndb_txn *txn,
   4216 				      struct ndb_filter *filter,
   4217 				      struct ndb_query_results *results,
   4218 				      int limit)
   4219 {
   4220 	MDB_cursor *cur;
   4221 	MDB_dbi db;
   4222 	MDB_val k, v;
   4223 	int rc, i, need_relays = 0;
   4224 	struct ndb_filter_elements *ids;
   4225 	struct ndb_note *note;
   4226 	struct ndb_query_result res;
   4227 	struct ndb_tsid tsid, *ptsid;
   4228 	uint64_t note_id, until, *pint;
   4229 	size_t note_size;
   4230 	unsigned char *id;
   4231 	struct ndb_note_relay_iterator note_relay_iter = {0};
   4232 	struct ndb_note_relay_iterator *relay_iter = NULL;
   4233 
   4234 	until = UINT64_MAX;
   4235 
   4236 	if (!(ids = ndb_filter_find_elements(filter, NDB_FILTER_IDS)))
   4237 		return 0;
   4238 
   4239 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4240 		until = *pint;
   4241 
   4242 	if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS))
   4243 		need_relays = 1;
   4244 
   4245 	db = txn->lmdb->dbs[NDB_DB_NOTE_ID];
   4246 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4247 		return 0;
   4248 
   4249 	// for each id in our ids filter, find in the db
   4250 	for (i = 0; i < ids->count; i++) {
   4251 		if (query_is_full(results, limit))
   4252 			break;
   4253 
   4254 		id = ndb_filter_get_id_element(filter, ids, i);
   4255 		ndb_tsid_init(&tsid, (unsigned char *)id, until);
   4256 
   4257 		k.mv_data = &tsid;
   4258 		k.mv_size = sizeof(tsid);
   4259 
   4260 		if (!ndb_cursor_start(cur, &k, &v))
   4261 			continue;
   4262 
   4263 		ptsid = (struct ndb_tsid *)k.mv_data;
   4264 		note_id = *(uint64_t*)v.mv_data;
   4265 
   4266 		if (memcmp(id, ptsid->id, 32))
   4267 			continue;
   4268 
   4269 		// get the note because we need it to match against the filter
   4270 		if (!(note = ndb_get_note_by_key(txn, note_id, &note_size)))
   4271 			continue;
   4272 
   4273 		relay_iter = need_relays ? &note_relay_iter : NULL;
   4274 		if (relay_iter)
   4275 			ndb_note_relay_iterate_start(txn, relay_iter, note_id);
   4276 
   4277 		// Sure this particular lookup matched the index query, but
   4278 		// does it match the entire filter? Check! We also pass in
   4279 		// things we've already matched via the filter so we don't have
   4280 		// to check again. This can be pretty important for filters
   4281 		// with a large number of entries.
   4282 		if (!ndb_filter_matches_with(filter, note, 1 << NDB_FILTER_IDS, relay_iter)) {
   4283 			ndb_note_relay_iterate_close(relay_iter);
   4284 			continue;
   4285 		}
   4286 		ndb_note_relay_iterate_close(relay_iter);
   4287 
   4288 		ndb_query_result_init(&res, note, note_size, note_id);
   4289 		if (!push_query_result(results, &res))
   4290 			break;
   4291 	}
   4292 
   4293 	mdb_cursor_close(cur);
   4294 	return 1;
   4295 }
   4296 
   4297 //
   4298 // encode a tag index key
   4299 //
   4300 // consists of:
   4301 //
   4302 //   u8   tag
   4303 //   u8   tag_val_len
   4304 //   [u8] tag_val_bytes
   4305 //   u64  created_at
   4306 //
   4307 static int ndb_encode_tag_key(unsigned char *buf, int buf_size,
   4308 			      char tag, const unsigned char *val,
   4309 			      unsigned char val_len,
   4310 			      uint64_t timestamp)
   4311 {
   4312 	struct cursor writer;
   4313 	int ok;
   4314 
   4315 	// quick exit for obvious case where it will be too big. There can be
   4316 	// values of val_len that still fail, but we just let the writer handle
   4317 	// those failure cases
   4318 	if (val_len >= buf_size)
   4319 		return 0;
   4320 
   4321 	make_cursor(buf, buf + buf_size, &writer);
   4322 
   4323 	ok =
   4324 		cursor_push_byte(&writer, tag) &&
   4325 		cursor_push(&writer, (unsigned char*)val, val_len) &&
   4326 		cursor_push(&writer, (unsigned char*)&timestamp, sizeof(timestamp));
   4327 
   4328 	if (!ok)
   4329 		return 0;
   4330 
   4331 	return writer.p - writer.start;
   4332 }
   4333 
   4334 static int ndb_query_plan_execute_authors(struct ndb_txn *txn,
   4335 					  struct ndb_filter *filter,
   4336 					  struct ndb_query_results *results,
   4337 					  int limit)
   4338 {
   4339 	MDB_val k, v;
   4340 	MDB_cursor *cur;
   4341 	int rc, i, need_relays = 0;
   4342 	uint64_t *pint, until, since, note_key;
   4343 	unsigned char *author;
   4344 	struct ndb_note *note;
   4345 	size_t note_size;
   4346 	struct ndb_filter_elements *authors;
   4347 	struct ndb_query_result res;
   4348 	struct ndb_tsid tsid, *ptsid;
   4349 	struct ndb_note_relay_iterator note_relay_iter;
   4350 	enum ndb_dbs db;
   4351 
   4352 	db = txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY];
   4353 
   4354 	if (!(authors = ndb_filter_find_elements(filter, NDB_FILTER_AUTHORS)))
   4355 		return 0;
   4356 
   4357 	until = UINT64_MAX;
   4358 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4359 		until = *pint;
   4360 
   4361 	since = 0;
   4362 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE)))
   4363 		since = *pint;
   4364 
   4365 	if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS))
   4366 		need_relays = 1;
   4367 
   4368 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4369 		return 0;
   4370 
   4371 	for (i = 0; i < authors->count; i++) {
   4372 		author = ndb_filter_get_id_element(filter, authors, i);
   4373 
   4374 		ndb_tsid_init(&tsid, author, until);
   4375 
   4376 		k.mv_data = &tsid;
   4377 		k.mv_size = sizeof(tsid);
   4378 
   4379 		if (!ndb_cursor_start(cur, &k, &v))
   4380 			continue;
   4381 
   4382 		// for each id in our ids filter, find in the db
   4383 		while (!query_is_full(results, limit)) {
   4384 			ptsid = (struct ndb_tsid *)k.mv_data;
   4385 			note_key = *(uint64_t*)v.mv_data;
   4386 
   4387 			// don't continue the scan if we're below `since`
   4388 			if (ptsid->timestamp < since)
   4389 				break;
   4390 
   4391 			// our author should match, if not bail
   4392 			if (memcmp(author, ptsid->id, 32))
   4393 				break;
   4394 
   4395 			// fetch the note, we need it for our query results
   4396 			// and to match further against the filter
   4397 			if (!(note = ndb_get_note_by_key(txn, note_key, &note_size)))
   4398 				goto next;
   4399 
   4400 			if (need_relays)
   4401 				ndb_note_relay_iterate_start(txn, &note_relay_iter, note_key);
   4402 
   4403 			if (!ndb_filter_matches_with(filter, note,
   4404 						     1 << NDB_FILTER_AUTHORS,
   4405 						     need_relays ? &note_relay_iter : NULL))
   4406 			{
   4407 				goto next;
   4408 			}
   4409 
   4410 			ndb_query_result_init(&res, note, note_size, note_key);
   4411 			if (!push_query_result(results, &res))
   4412 				break;
   4413 
   4414 next:
   4415 			if (mdb_cursor_get(cur, &k, &v, MDB_PREV))
   4416 				break;
   4417 		}
   4418 	}
   4419 
   4420 	mdb_cursor_close(cur);
   4421 	return 1;
   4422 }
   4423 
   4424 static int ndb_query_plan_execute_created_at(struct ndb_txn *txn,
   4425 					     struct ndb_filter *filter,
   4426 					     struct ndb_query_results *results,
   4427 					     int limit)
   4428 {
   4429 	MDB_dbi db;
   4430 	MDB_val k, v;
   4431 	MDB_cursor *cur;
   4432 	int rc, need_relays = 0;
   4433 	struct ndb_note *note;
   4434 	struct ndb_tsid key, *pkey;
   4435 	uint64_t *pint, until, since, note_id;
   4436 	size_t note_size;
   4437 	struct ndb_query_result res;
   4438 	struct ndb_note_relay_iterator note_relay_iter;
   4439 	unsigned char high_key[32] = {0xFF};
   4440 
   4441 	db = txn->lmdb->dbs[NDB_DB_NOTE_ID];
   4442 
   4443 	until = UINT64_MAX;
   4444 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4445 		until = *pint;
   4446 
   4447 	since = 0;
   4448 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE)))
   4449 		since = *pint;
   4450 
   4451 	if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS))
   4452 		need_relays = 1;
   4453 
   4454 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4455 		return 0;
   4456 
   4457 	// if we have until, start there, otherwise just use max
   4458 	ndb_tsid_init(&key, high_key, until);
   4459 	k.mv_data = &key;
   4460 	k.mv_size = sizeof(key);
   4461 
   4462 	if (!ndb_cursor_start(cur, &k, &v))
   4463 		return 1;
   4464 
   4465 	while (!query_is_full(results, limit)) {
   4466 		pkey = (struct ndb_tsid *)k.mv_data;
   4467 		note_id = *(uint64_t*)v.mv_data;
   4468 		assert(v.mv_size == 8);
   4469 
   4470 		// don't continue the scan if we're below `since`
   4471 		if (pkey->timestamp < since)
   4472 			break;
   4473 
   4474 		if (!(note = ndb_get_note_by_key(txn, note_id, &note_size)))
   4475 			goto next;
   4476 
   4477 		if (need_relays)
   4478 			ndb_note_relay_iterate_start(txn, &note_relay_iter, note_id);
   4479 
   4480 		// does this entry match our filter?
   4481 		if (!ndb_filter_matches_with(filter, note, 0, need_relays ? &note_relay_iter : NULL))
   4482 			goto next;
   4483 
   4484 		ndb_query_result_init(&res, note, (uint64_t)note_size, note_id);
   4485 		if (!push_query_result(results, &res))
   4486 			break;
   4487 next:
   4488 		if (mdb_cursor_get(cur, &k, &v, MDB_PREV))
   4489 			break;
   4490 	}
   4491 
   4492 	mdb_cursor_close(cur);
   4493 	return 1;
   4494 }
   4495 
   4496 static int ndb_query_plan_execute_tags(struct ndb_txn *txn,
   4497 				       struct ndb_filter *filter,
   4498 				       struct ndb_query_results *results,
   4499 				       int limit)
   4500 {
   4501 	MDB_cursor *cur;
   4502 	MDB_dbi db;
   4503 	MDB_val k, v;
   4504 	int len, taglen, rc, i, need_relays = 0;
   4505 	uint64_t *pint, until, note_id;
   4506 	size_t note_size;
   4507 	unsigned char key_buffer[255];
   4508 	struct ndb_note *note;
   4509 	struct ndb_filter_elements *tags;
   4510 	unsigned char *tag;
   4511 	struct ndb_query_result res;
   4512 	struct ndb_note_relay_iterator note_relay_iter;
   4513 
   4514 	db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
   4515 
   4516 	if (!(tags = ndb_filter_find_elements(filter, NDB_FILTER_TAGS)))
   4517 		return 0;
   4518 
   4519 	if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS))
   4520 		need_relays = 1;
   4521 
   4522 	until = UINT64_MAX;
   4523 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4524 		until = *pint;
   4525 
   4526 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4527 		return 0;
   4528 
   4529 	for (i = 0; i < tags->count; i++) {
   4530 		tag = ndb_filter_get_id_element(filter, tags, i);
   4531 
   4532 		taglen = tags->field.elem_type == NDB_ELEMENT_ID
   4533 		       ? 32 : strlen((const char*)tag);
   4534 
   4535 		if (!(len = ndb_encode_tag_key(key_buffer, sizeof(key_buffer),
   4536 					       tags->field.tag, tag, taglen,
   4537 					       until))) {
   4538 			goto fail;
   4539 		}
   4540 
   4541 		k.mv_data = key_buffer;
   4542 		k.mv_size = len;
   4543 
   4544 		if (!ndb_cursor_start(cur, &k, &v))
   4545 			continue;
   4546 
   4547 		// for each id in our ids filter, find in the db
   4548 		while (!query_is_full(results, limit)) {
   4549 			// check if tag value matches, bail if not
   4550 			if (((unsigned char *)k.mv_data)[0] != tags->field.tag)
   4551 				break;
   4552 
   4553 			// check if tag value matches, bail if not
   4554 			if (taglen != k.mv_size - 9)
   4555 				break;
   4556 
   4557 			if (memcmp((unsigned char *)k.mv_data+1, tag, k.mv_size-9))
   4558 				break;
   4559 
   4560 			note_id = *(uint64_t*)v.mv_data;
   4561 
   4562 			if (!(note = ndb_get_note_by_key(txn, note_id, &note_size)))
   4563 				goto next;
   4564 
   4565 			if (need_relays)
   4566 				ndb_note_relay_iterate_start(txn, &note_relay_iter, note_id);
   4567 
   4568 			if (!ndb_filter_matches_with(filter, note,
   4569 						     1 << NDB_FILTER_TAGS,
   4570 						     need_relays ? &note_relay_iter : NULL))
   4571 				goto next;
   4572 
   4573 			ndb_query_result_init(&res, note, note_size, note_id);
   4574 			if (!push_query_result(results, &res))
   4575 				break;
   4576 
   4577 next:
   4578 			if (mdb_cursor_get(cur, &k, &v, MDB_PREV))
   4579 				break;
   4580 		}
   4581 	}
   4582 
   4583 	mdb_cursor_close(cur);
   4584 	return 1;
   4585 fail:
   4586 	mdb_cursor_close(cur);
   4587 	return 0;
   4588 }
   4589 
   4590 static int ndb_query_plan_execute_author_kinds(
   4591 		struct ndb_txn *txn,
   4592 		struct ndb_filter *filter,
   4593 		struct ndb_query_results *results,
   4594 		int limit)
   4595 {
   4596 	MDB_cursor *cur;
   4597 	MDB_dbi db;
   4598 	MDB_val k, v;
   4599 	struct ndb_note *note;
   4600 	struct ndb_filter_elements *kinds, *relays, *authors;
   4601 	struct ndb_query_result res;
   4602 	uint64_t kind, note_id, until, since, *pint;
   4603 	size_t note_size;
   4604 	unsigned char *author;
   4605 	int i, j, rc;
   4606 	struct ndb_id_u64_ts key, *pkey;
   4607 	struct ndb_note_relay_iterator note_relay_iter;
   4608 
   4609 	// we should have kinds in a kinds filter!
   4610 	if (!(kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS)))
   4611 		return 0;
   4612 	//
   4613 	// we should have kinds in a kinds filter!
   4614 	if (!(authors = ndb_filter_find_elements(filter, NDB_FILTER_AUTHORS)))
   4615 		return 0;
   4616 
   4617 	relays = ndb_filter_find_elements(filter, NDB_FILTER_RELAYS);
   4618 
   4619 	until = UINT64_MAX;
   4620 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4621 		until = *pint;
   4622 
   4623 	since = 0;
   4624 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE)))
   4625 		since = *pint;
   4626 
   4627 	db = txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND];
   4628 
   4629 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4630 		return 0;
   4631 
   4632 	for (j = 0; j < authors->count; j++) {
   4633 		if (query_is_full(results, limit))
   4634 			break;
   4635 
   4636 		if (!(author = ndb_filter_get_id_element(filter, authors, j)))
   4637 			continue;
   4638 
   4639 	for (i = 0; i < kinds->count; i++) {
   4640 		if (query_is_full(results, limit))
   4641 			break;
   4642 
   4643 		kind = kinds->elements[i];
   4644 
   4645 		ndb_debug("finding kind %"PRIu64"\n", kind);
   4646 
   4647 		ndb_id_u64_ts_init(&key, author, kind, until);
   4648 		
   4649 		k.mv_data = &key;
   4650 		k.mv_size = sizeof(key);
   4651 
   4652 		if (!ndb_cursor_start(cur, &k, &v))
   4653 			continue;
   4654 
   4655 		// scan the kind subindex
   4656 		while (!query_is_full(results, limit)) {
   4657 			pkey = (struct ndb_id_u64_ts*)k.mv_data;
   4658 
   4659 			ndb_debug("scanning subindex kind:%"PRIu64" created_at:%"PRIu64" pubkey:",
   4660 					pkey->u64,
   4661 					pkey->timestamp);
   4662 
   4663 			if (pkey->u64 != kind)
   4664 				break;
   4665 
   4666 			// don't continue the scan if we're below `since`
   4667 			if (pkey->timestamp < since)
   4668 				break;
   4669 
   4670 			if (memcmp(pkey->id, author, 32))
   4671 				break;
   4672 
   4673 			note_id = *(uint64_t*)v.mv_data;
   4674 			if (!(note = ndb_get_note_by_key(txn, note_id, &note_size)))
   4675 				goto next;
   4676 
   4677 			if (relays)
   4678 				ndb_note_relay_iterate_start(txn, &note_relay_iter, note_id);
   4679 
   4680 			if (!ndb_filter_matches_with(filter, note,
   4681 						     (1 << NDB_FILTER_KINDS) | (1 << NDB_FILTER_AUTHORS),
   4682 						     relays? &note_relay_iter : NULL))
   4683 				goto next;
   4684 
   4685 			ndb_query_result_init(&res, note, note_size, note_id);
   4686 			if (!push_query_result(results, &res))
   4687 				break;
   4688 
   4689 next:
   4690 			if (mdb_cursor_get(cur, &k, &v, MDB_PREV))
   4691 				break;
   4692 		}
   4693 	}
   4694 	}
   4695 
   4696 	mdb_cursor_close(cur);
   4697 	return 1;
   4698 }
   4699 
   4700 static int ndb_query_plan_execute_profile_search(
   4701 		struct ndb_txn *txn,
   4702 		struct ndb_filter *filter,
   4703 		struct ndb_query_results *results,
   4704 		int limit)
   4705 {
   4706 	const char *search;
   4707 	int i;
   4708 
   4709 	// The filter pubkey is updated inplace for each note search
   4710 	unsigned char *filter_pubkey;
   4711 	unsigned char pubkey[32] = {0};
   4712 	struct ndb_filter_elements *els;
   4713 	struct ndb_search profile_search;
   4714 	struct ndb_filter note_filter, *f = &note_filter;
   4715 
   4716 	if (!(search = ndb_filter_find_search(filter)))
   4717 		return 0;
   4718 
   4719 	if (!ndb_filter_init_with(f, 1))
   4720 		return 0;
   4721 
   4722 	ndb_filter_start_field(f, NDB_FILTER_KINDS);
   4723 	ndb_filter_add_int_element(f, 0);
   4724 	ndb_filter_end_field(f);
   4725 
   4726 	ndb_filter_start_field(f, NDB_FILTER_AUTHORS);
   4727 	ndb_filter_add_id_element(f, pubkey);
   4728 	ndb_filter_end_field(f);
   4729 	ndb_filter_end(f);
   4730 
   4731 	// get the authors element after we finalize the filter, since
   4732 	// the data could have moved
   4733 	if (!(els = ndb_filter_find_elements(f, NDB_FILTER_AUTHORS)))
   4734 		goto fail;
   4735 
   4736 	// grab pointer to pubkey in the filter so that we can
   4737 	// update the filter as we go
   4738 	if (!(filter_pubkey = ndb_filter_get_id_element(f, els, 0)))
   4739 		goto fail;
   4740 
   4741 	for (i = 0; !query_is_full(results, limit); i++) {
   4742 		if (i == 0) {
   4743 			if (!ndb_search_profile(txn, &profile_search, search))
   4744 				break;
   4745 		} else {
   4746 			if (!ndb_search_profile_next(&profile_search))
   4747 				break;
   4748 		}
   4749 
   4750 		// Copy pubkey into filter
   4751 		memcpy(filter_pubkey, profile_search.key->id, 32);
   4752 
   4753 		// Look up the corresponding note associated with that pubkey
   4754 		if (!ndb_query_plan_execute_author_kinds(txn, f, results, limit))
   4755 			goto fail;
   4756 	}
   4757 
   4758 	ndb_search_profile_end(&profile_search);
   4759 	ndb_filter_destroy(f);
   4760 	return 1;
   4761 
   4762 fail:
   4763 	ndb_filter_destroy(f);
   4764 	return 0;
   4765 }
   4766 
   4767 static int ndb_query_plan_execute_relay_kinds(
   4768 		struct ndb_txn *txn,
   4769 		struct ndb_filter *filter,
   4770 		struct ndb_query_results *results,
   4771 		int limit)
   4772 {
   4773 	MDB_cursor *cur;
   4774 	MDB_dbi db;
   4775 	MDB_val k, v;
   4776 	struct ndb_note *note;
   4777 	struct ndb_filter_elements *kinds, *relays;
   4778 	struct ndb_query_result res;
   4779 	uint64_t kind, note_id, until, since, *pint;
   4780 	size_t note_size;
   4781 	const char *relay;
   4782 	int i, j, rc, len;
   4783 	struct ndb_relay_kind_key relay_key;
   4784 	unsigned char keybuf[256];
   4785 
   4786 	// we should have kinds in a kinds filter!
   4787 	if (!(kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS)))
   4788 		return 0;
   4789 
   4790 	if (!(relays = ndb_filter_find_elements(filter, NDB_FILTER_RELAYS)))
   4791 		return 0;
   4792 
   4793 	until = UINT64_MAX;
   4794 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4795 		until = *pint;
   4796 
   4797 	since = 0;
   4798 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE)))
   4799 		since = *pint;
   4800 
   4801 	db = txn->lmdb->dbs[NDB_DB_NOTE_RELAY_KIND];
   4802 
   4803 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4804 		return 0;
   4805 
   4806 	for (j = 0; j < relays->count; j++) {
   4807 		if (query_is_full(results, limit))
   4808 			break;
   4809 
   4810 		if (!(relay = ndb_filter_get_string_element(filter, relays, j)))
   4811 			continue;
   4812 
   4813 	for (i = 0; i < kinds->count; i++) {
   4814 		if (query_is_full(results, limit))
   4815 			break;
   4816 
   4817 		kind = kinds->elements[i];
   4818 		ndb_debug("kind %" PRIu64 "\n", kind);
   4819 		
   4820 		if (!ndb_relay_kind_key_init_high(&relay_key, relay, kind, until)) {
   4821 			ndb_debug("ndb_relay_kind_key_init_high failed in relay query\n");
   4822 			continue;
   4823 		}
   4824 
   4825 		if (!(len = ndb_build_relay_kind_key(keybuf, sizeof(keybuf), &relay_key))) {
   4826 			ndb_debug("ndb_build_relay_kind_key failed in relay query\n");
   4827 			ndb_debug_relay_kind_key(&relay_key);
   4828 			continue;
   4829 		}
   4830 
   4831 		k.mv_data = keybuf;
   4832 		k.mv_size = len;
   4833 
   4834 		ndb_debug("starting with key ");
   4835 		ndb_debug_relay_kind_key(&relay_key);
   4836 
   4837 		if (!ndb_cursor_start(cur, &k, &v))
   4838 			continue;
   4839 
   4840 		// scan the kind subindex
   4841 		while (!query_is_full(results, limit)) {
   4842 			ndb_parse_relay_kind_key(&relay_key, k.mv_data);
   4843 
   4844 			ndb_debug("inside kind subindex ");
   4845 			ndb_debug_relay_kind_key(&relay_key);
   4846 
   4847 			if (relay_key.kind != kind)
   4848 				break;
   4849 
   4850 			if (strcmp(relay_key.relay, relay))
   4851 				break;
   4852 
   4853 			// don't continue the scan if we're below `since`
   4854 			if (relay_key.created_at < since)
   4855 				break;
   4856 
   4857 			note_id = relay_key.note_key;
   4858 			if (!(note = ndb_get_note_by_key(txn, note_id, &note_size)))
   4859 				goto next;
   4860 
   4861 			if (!ndb_filter_matches_with(filter, note,
   4862 						     (1 << NDB_FILTER_KINDS) | (1 << NDB_FILTER_RELAYS),
   4863 						     NULL))
   4864 				goto next;
   4865 
   4866 			ndb_query_result_init(&res, note, note_size, note_id);
   4867 			if (!push_query_result(results, &res))
   4868 				break;
   4869 
   4870 next:
   4871 			if (mdb_cursor_get(cur, &k, &v, MDB_PREV))
   4872 				break;
   4873 		}
   4874 	}
   4875 	}
   4876 
   4877 	mdb_cursor_close(cur);
   4878 	return 1;
   4879 }
   4880 
   4881 static int ndb_query_plan_execute_kinds(struct ndb_txn *txn,
   4882 					struct ndb_filter *filter,
   4883 					struct ndb_query_results *results,
   4884 					int limit)
   4885 {
   4886 	MDB_cursor *cur;
   4887 	MDB_dbi db;
   4888 	MDB_val k, v;
   4889 	struct ndb_note *note;
   4890 	struct ndb_u64_ts tsid, *ptsid;
   4891 	struct ndb_filter_elements *kinds;
   4892 	struct ndb_query_result res;
   4893 	uint64_t kind, note_id, until, since, *pint;
   4894 	size_t note_size;
   4895 	int i, rc, need_relays = 0;
   4896 	struct ndb_note_relay_iterator note_relay_iter;
   4897 
   4898 	// we should have kinds in a kinds filter!
   4899 	if (!(kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS)))
   4900 		return 0;
   4901 
   4902 	if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS))
   4903 		need_relays = 1;
   4904 
   4905 	until = UINT64_MAX;
   4906 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   4907 		until = *pint;
   4908 
   4909 	since = 0;
   4910 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE)))
   4911 		since = *pint;
   4912 
   4913 	db = txn->lmdb->dbs[NDB_DB_NOTE_KIND];
   4914 
   4915 	if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur)))
   4916 		return 0;
   4917 
   4918 	for (i = 0; i < kinds->count; i++) {
   4919 		if (query_is_full(results, limit))
   4920 			break;
   4921 
   4922 		kind = kinds->elements[i];
   4923 		ndb_debug("kind %" PRIu64 "\n", kind);
   4924 		ndb_u64_ts_init(&tsid, kind, until);
   4925 
   4926 		k.mv_data = &tsid;
   4927 		k.mv_size = sizeof(tsid);
   4928 
   4929 		if (!ndb_cursor_start(cur, &k, &v))
   4930 			continue;
   4931 
   4932 		// for each id in our ids filter, find in the db
   4933 		while (!query_is_full(results, limit)) {
   4934 			ptsid = (struct ndb_u64_ts *)k.mv_data;
   4935 			if (ptsid->u64 != kind)
   4936 				break;
   4937 
   4938 			// don't continue the scan if we're below `since`
   4939 			if (ptsid->timestamp < since)
   4940 				break;
   4941 
   4942 			note_id = *(uint64_t*)v.mv_data;
   4943 			if (!(note = ndb_get_note_by_key(txn, note_id, &note_size)))
   4944 				goto next;
   4945 
   4946 			if (need_relays)
   4947 				ndb_note_relay_iterate_start(txn, &note_relay_iter, note_id);
   4948 
   4949 			if (!ndb_filter_matches_with(filter, note,
   4950 						     1 << NDB_FILTER_KINDS,
   4951 						     need_relays ? &note_relay_iter : NULL))
   4952 				goto next;
   4953 
   4954 			ndb_query_result_init(&res, note, note_size, note_id);
   4955 			if (!push_query_result(results, &res))
   4956 				break;
   4957 
   4958 next:
   4959 			if (mdb_cursor_get(cur, &k, &v, MDB_PREV))
   4960 				break;
   4961 		}
   4962 	}
   4963 
   4964 	mdb_cursor_close(cur);
   4965 	return 1;
   4966 }
   4967 
   4968 static enum ndb_query_plan ndb_filter_plan(struct ndb_filter *filter)
   4969 {
   4970 	struct ndb_filter_elements *ids, *kinds, *authors, *tags, *search, *relays;
   4971 
   4972 	ids = ndb_filter_find_elements(filter, NDB_FILTER_IDS);
   4973 	search = ndb_filter_find_elements(filter, NDB_FILTER_SEARCH);
   4974 	kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS);
   4975 	authors = ndb_filter_find_elements(filter, NDB_FILTER_AUTHORS);
   4976 	tags = ndb_filter_find_elements(filter, NDB_FILTER_TAGS);
   4977 	relays = ndb_filter_find_elements(filter, NDB_FILTER_RELAYS);
   4978 
   4979 	// profile search
   4980 	if (kinds && kinds->count == 1 && kinds->elements[0] == 0 && search) {
   4981 		return NDB_PLAN_PROFILE_SEARCH;
   4982 	}
   4983 
   4984 	// TODO: fix multi-author queries
   4985 	if (search) {
   4986 		return NDB_PLAN_SEARCH;
   4987 	} else if (ids) {
   4988 		return NDB_PLAN_IDS;
   4989 	} else if (relays && kinds && !authors) {
   4990 		return NDB_PLAN_RELAY_KINDS;
   4991 	} else if (kinds && authors && authors->count == 1) {
   4992 		return NDB_PLAN_AUTHOR_KINDS;
   4993 	} else if (authors && authors->count == 1) {
   4994 		return NDB_PLAN_AUTHORS;
   4995 	} else if (tags && tags->count == 1) {
   4996 		return NDB_PLAN_TAGS;
   4997 	} else if (kinds) {
   4998 		return NDB_PLAN_KINDS;
   4999 	}
   5000 
   5001 	return NDB_PLAN_CREATED;
   5002 }
   5003 
   5004 static const char *ndb_query_plan_name(enum ndb_query_plan plan_id)
   5005 {
   5006 	switch (plan_id) {
   5007 		case NDB_PLAN_IDS:     return "ids";
   5008 		case NDB_PLAN_SEARCH:  return "search";
   5009 		case NDB_PLAN_KINDS:   return "kinds";
   5010 		case NDB_PLAN_TAGS:    return "tags";
   5011 		case NDB_PLAN_CREATED: return "created";
   5012 		case NDB_PLAN_AUTHORS: return "authors";
   5013 		case NDB_PLAN_RELAY_KINDS: return "relay_kinds";
   5014 		case NDB_PLAN_AUTHOR_KINDS: return "author_kinds";
   5015 		case NDB_PLAN_PROFILE_SEARCH: return "profile_search";
   5016 	}
   5017 
   5018 	return "unknown";
   5019 }
   5020 
   5021 static int ndb_query_filter(struct ndb_txn *txn, struct ndb_filter *filter,
   5022 			    struct ndb_query_result *res, int capacity,
   5023 			    int *results_out)
   5024 {
   5025 	struct ndb_query_results results;
   5026 	uint64_t limit, *pint;
   5027 	enum ndb_query_plan plan;
   5028 	limit = capacity;
   5029 
   5030 	if ((pint = ndb_filter_get_int(filter, NDB_FILTER_LIMIT)))
   5031 		limit = *pint;
   5032 
   5033 	limit = min(capacity, limit);
   5034 	make_cursor((unsigned char *)res,
   5035 		    ((unsigned char *)res) + limit * sizeof(*res),
   5036 		    &results.cur);
   5037 
   5038 	plan = ndb_filter_plan(filter);
   5039 	ndb_debug("using query plan '%s'\n", ndb_query_plan_name(plan));
   5040 	switch (plan) {
   5041 	// We have a list of ids, just open a cursor and jump to each once
   5042 	case NDB_PLAN_IDS:
   5043 		if (!ndb_query_plan_execute_ids(txn, filter, &results, limit))
   5044 			return 0;
   5045 		break;
   5046 	case NDB_PLAN_RELAY_KINDS:
   5047 		if (!ndb_query_plan_execute_relay_kinds(txn, filter, &results, limit))
   5048 			return 0;
   5049 		break;
   5050 	case NDB_PLAN_SEARCH:
   5051 		if (!ndb_query_plan_execute_search(txn, filter, &results, limit))
   5052 			return 0;
   5053 		break;
   5054 
   5055 	case NDB_PLAN_PROFILE_SEARCH:
   5056 		if (!ndb_query_plan_execute_profile_search(txn, filter, &results, limit))
   5057 			return 0;
   5058 		break;
   5059 
   5060 	// We have just kinds, just scan the kind index
   5061 	case NDB_PLAN_KINDS:
   5062 		if (!ndb_query_plan_execute_kinds(txn, filter, &results, limit))
   5063 			return 0;
   5064 		break;
   5065 	case NDB_PLAN_TAGS:
   5066 		if (!ndb_query_plan_execute_tags(txn, filter, &results, limit))
   5067 			return 0;
   5068 		break;
   5069 	case NDB_PLAN_CREATED:
   5070 		if (!ndb_query_plan_execute_created_at(txn, filter, &results, limit))
   5071 			return 0;
   5072 		break;
   5073 	case NDB_PLAN_AUTHORS:
   5074 		if (!ndb_query_plan_execute_authors(txn, filter, &results, limit))
   5075 			return 0;
   5076 		break;
   5077 	case NDB_PLAN_AUTHOR_KINDS:
   5078 		if (!ndb_query_plan_execute_author_kinds(txn, filter, &results, limit))
   5079 			return 0;
   5080 		break;
   5081 	}
   5082 
   5083 	*results_out = cursor_count(&results.cur, sizeof(*res));
   5084 	return 1;
   5085 }
   5086 
   5087 int ndb_query(struct ndb_txn *txn, struct ndb_filter *filters, int num_filters,
   5088 	      struct ndb_query_result *results, int result_capacity, int *count)
   5089 {
   5090 	int i, out;
   5091 	struct ndb_query_result *p = results;
   5092 
   5093 	out = 0;
   5094 	*count = 0;
   5095 
   5096 	for (i = 0; i < num_filters; i++) {
   5097 		if (!ndb_query_filter(txn, &filters[i], p,
   5098 				      result_capacity, &out)) {
   5099 			return 0;
   5100 		}
   5101 
   5102 		*count += out;
   5103 		p += out;
   5104 		result_capacity -= out;
   5105 		if (result_capacity <= 0)
   5106 			break;
   5107 	}
   5108 
   5109 	// sort results
   5110 	qsort(results, *count, sizeof(*results), compare_query_results);
   5111 	return 1;
   5112 }
   5113 
   5114 static int ndb_write_note_tag_index(struct ndb_txn *txn, struct ndb_note *note,
   5115 				    uint64_t note_key)
   5116 {
   5117 	unsigned char key_buffer[255];
   5118 	struct ndb_iterator iter;
   5119 	struct ndb_str tkey, tval;
   5120 	char tchar;
   5121 	int len, rc;
   5122 	MDB_val key, val;
   5123 	MDB_dbi tags_db;
   5124 
   5125 	tags_db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS];
   5126 
   5127 	ndb_tags_iterate_start(note, &iter);
   5128 
   5129 	while (ndb_tags_iterate_next(&iter)) {
   5130 		if (iter.tag->count < 2)
   5131 			continue;
   5132 
   5133 		tkey = ndb_tag_str(note, iter.tag, 0);
   5134 
   5135 		// we only write indices for 1-char tags.
   5136 		tchar = tkey.str[0];
   5137 		if (tchar == 0 || tkey.str[1] != 0)
   5138 			continue;
   5139 
   5140 		tval = ndb_tag_str(note, iter.tag, 1);
   5141 		len = ndb_str_len(&tval);
   5142 
   5143 		if (!(len = ndb_encode_tag_key(key_buffer, sizeof(key_buffer),
   5144 					       tchar, tval.id, (unsigned char)len,
   5145 					       ndb_note_created_at(note)))) {
   5146 			// this will fail when we try to encode a key that is
   5147 			// too big
   5148 			continue;
   5149 		}
   5150 
   5151 		//ndb_debug("writing tag '%c':'data:%d' to index\n", tchar, len);
   5152 
   5153 		key.mv_data = key_buffer;
   5154 		key.mv_size = len;
   5155 
   5156 		val.mv_data = &note_key;
   5157 		val.mv_size = sizeof(note_key);
   5158 
   5159 		if ((rc = mdb_put(txn->mdb_txn, tags_db, &key, &val, 0))) {
   5160 			ndb_debug("write note tag index to db failed: %s\n",
   5161 					mdb_strerror(rc));
   5162 			return 0;
   5163 		}
   5164 	}
   5165 
   5166 	return 1;
   5167 }
   5168 
   5169 static int ndb_write_note_kind_index(struct ndb_txn *txn, struct ndb_note *note,
   5170 				     uint64_t note_key)
   5171 {
   5172 	struct ndb_u64_ts tsid;
   5173 	int rc;
   5174 	MDB_val key, val;
   5175 	MDB_dbi kind_db;
   5176 
   5177 	ndb_u64_ts_init(&tsid, note->kind, note->created_at);
   5178 
   5179 	key.mv_data = &tsid;
   5180 	key.mv_size = sizeof(tsid);
   5181 	val.mv_data = &note_key;
   5182 	val.mv_size = sizeof(note_key);
   5183 
   5184 	kind_db = txn->lmdb->dbs[NDB_DB_NOTE_KIND];
   5185 
   5186 	if ((rc = mdb_put(txn->mdb_txn, kind_db, &key, &val, 0))) {
   5187 		ndb_debug("write note kind index to db failed: %s\n",
   5188 				mdb_strerror(rc));
   5189 		return 0;
   5190 	}
   5191 
   5192 	return 1;
   5193 }
   5194 
   5195 static int ndb_write_word_to_index(struct ndb_txn *txn, const char *word,
   5196 				   int word_len, int word_index,
   5197 				   uint64_t timestamp, uint64_t note_id)
   5198 {
   5199 	// cap to some reasonable key size
   5200 	unsigned char buffer[1024];
   5201 	int keysize, rc;
   5202 	MDB_val k, v;
   5203 	MDB_dbi text_db;
   5204 
   5205 	// build our compressed text index key
   5206 	if (!ndb_make_text_search_key(buffer, sizeof(buffer), word_index,
   5207 				      word_len, word, timestamp, note_id,
   5208 				      &keysize)) {
   5209 		// probably too big
   5210 
   5211 		return 0;
   5212 	}
   5213 
   5214 	k.mv_data = buffer;
   5215 	k.mv_size = keysize;
   5216 
   5217 	v.mv_data = NULL;
   5218 	v.mv_size = 0;
   5219 
   5220 	text_db = txn->lmdb->dbs[NDB_DB_NOTE_TEXT];
   5221 
   5222 	if ((rc = mdb_put(txn->mdb_txn, text_db, &k, &v, 0))) {
   5223 		ndb_debug("write note text index to db failed: %s\n",
   5224 				mdb_strerror(rc));
   5225 		return 0;
   5226 	}
   5227 
   5228 	return 1;
   5229 }
   5230 
   5231 
   5232 
   5233 // break a string into individual words for querying or for building the
   5234 // fulltext search index. This is callback based so we don't need to
   5235 // build up an intermediate structure
   5236 static int ndb_parse_words(struct cursor *cur, void *ctx, ndb_word_parser_fn fn)
   5237 {
   5238 	int word_len, words;
   5239 	const char *word;
   5240 
   5241 	words = 0;
   5242 
   5243 	while (cur->p < cur->end) {
   5244 		consume_whitespace_or_punctuation(cur);
   5245 		if (cur->p >= cur->end)
   5246 			break;
   5247 		word = (const char *)cur->p;
   5248 
   5249 		if (!consume_until_boundary(cur))
   5250 			break;
   5251 
   5252 		// start of word or end
   5253 		word_len = cur->p - (unsigned char *)word;
   5254 		if (word_len == 0 && cur->p >= cur->end)
   5255 			break;
   5256 
   5257 		if (word_len == 0) {
   5258 			if (!cursor_skip(cur, 1))
   5259 				break;
   5260 			continue;
   5261 		}
   5262 
   5263 		//ndb_debug("writing word index '%.*s'\n", word_len, word);
   5264 
   5265 		if (!fn(ctx, word, word_len, words))
   5266 			continue;
   5267 
   5268 		words++;
   5269 	}
   5270 
   5271 	return 1;
   5272 }
   5273 
   5274 struct ndb_word_writer_ctx
   5275 {
   5276 	struct ndb_txn *txn;
   5277 	struct ndb_note *note;
   5278 	uint64_t note_id;
   5279 };
   5280 
   5281 static int ndb_fulltext_word_writer(void *ctx,
   5282 		const char *word, int word_len, int words)
   5283 {
   5284 	struct ndb_word_writer_ctx *wctx = ctx;
   5285 
   5286 	if (!ndb_write_word_to_index(wctx->txn, word, word_len, words,
   5287 				     wctx->note->created_at, wctx->note_id)) {
   5288 		// too big to write this one, just skip it
   5289 		ndb_debug("failed to write word '%.*s' to index\n", word_len, word);
   5290 
   5291 		return 0;
   5292 	}
   5293 
   5294 	//fprintf(stderr, "wrote '%.*s' to note text index\n", word_len, word);
   5295 	return 1;
   5296 }
   5297 
   5298 static int ndb_write_note_fulltext_index(struct ndb_txn *txn,
   5299 					 struct ndb_note *note,
   5300 					 uint64_t note_id)
   5301 {
   5302 	struct cursor cur;
   5303 	unsigned char *content;
   5304 	struct ndb_str str;
   5305 	struct ndb_word_writer_ctx ctx;
   5306 
   5307 	str = ndb_note_str(note, &note->content);
   5308 	// I don't think this should happen?
   5309 	if (unlikely(str.flag == NDB_PACKED_ID))
   5310 		return 0;
   5311 
   5312 	content = (unsigned char *)str.str;
   5313 
   5314 	make_cursor(content, content + note->content_length, &cur);
   5315 
   5316 	ctx.txn = txn;
   5317 	ctx.note = note;
   5318 	ctx.note_id = note_id;
   5319 
   5320 	ndb_parse_words(&cur, &ctx, ndb_fulltext_word_writer);
   5321 
   5322 	return 1;
   5323 }
   5324 
   5325 static int ndb_parse_search_words(void *ctx, const char *word_str, int word_len, int word_index)
   5326 {
   5327 	(void)word_index;
   5328 	struct ndb_search_words *words = ctx;
   5329 	struct ndb_word *word;
   5330 
   5331 	if (words->num_words + 1 > MAX_TEXT_SEARCH_WORDS)
   5332 		return 0;
   5333 
   5334 	word = &words->words[words->num_words++];
   5335 	word->word = word_str;
   5336 	word->word_len = word_len;
   5337 
   5338 	return 1;
   5339 }
   5340 
   5341 static void ndb_search_words_init(struct ndb_search_words *words)
   5342 {
   5343 	words->num_words = 0;
   5344 }
   5345 
   5346 static int prefix_count(const char *str1, int len1, const char *str2, int len2) {
   5347 	int i, count = 0;
   5348 	int min_len = len1 < len2 ? len1 : len2;
   5349 
   5350 	for (i = 0; i < min_len; i++) {
   5351 		// case insensitive
   5352 		if (tolower(str1[i]) == tolower(str2[i]))
   5353 			count++;
   5354 		else
   5355 			break;
   5356 	}
   5357 
   5358 	return count;
   5359 }
   5360 
   5361 static int ndb_prefix_matches(struct ndb_text_search_result *result,
   5362 			      struct ndb_word *search_word)
   5363 {
   5364 	// Empty strings shouldn't happen but let's
   5365 	if (result->key.str_len < 2 || search_word->word_len < 2)
   5366 		return 0;
   5367 
   5368 	// make sure we at least have two matching prefix characters. exact
   5369 	// matches are nice but range searches allow us to match prefixes as
   5370 	// well. A double-char prefix is suffient, but maybe we could up this
   5371 	// in the future.
   5372 	//
   5373 	// TODO: How are we handling utf-8 prefix matches like
   5374 	// japanese?
   5375 	//
   5376 	if (   result->key.str[0] != tolower(search_word->word[0])
   5377 	    && result->key.str[1] != tolower(search_word->word[1])
   5378 	    )
   5379 		return 0;
   5380 
   5381 	// count the number of prefix-matched characters. This will be used
   5382 	// for ranking search results
   5383 	result->prefix_chars = prefix_count(result->key.str,
   5384 					    result->key.str_len,
   5385 					    search_word->word,
   5386 					    search_word->word_len);
   5387 
   5388 	if (result->prefix_chars <= (int)((double)search_word->word_len / 1.5))
   5389 		return 0;
   5390 
   5391 	return 1;
   5392 }
   5393 
   5394 // This is called when scanning the full text search index. Scanning stops
   5395 // when we no longer have a prefix match for the word
   5396 static int ndb_text_search_next_word(MDB_cursor *cursor, MDB_cursor_op op,
   5397 	MDB_val *k, struct ndb_word *search_word,
   5398 	struct ndb_text_search_result *last_result,
   5399 	struct ndb_text_search_result *result,
   5400 	MDB_cursor_op order_op)
   5401 {
   5402 	struct cursor key_cursor;
   5403 	//struct ndb_text_search_key search_key;
   5404 	MDB_val v;
   5405 	int retries;
   5406 	retries = -1;
   5407 
   5408 	make_cursor(k->mv_data, (unsigned char *)k->mv_data + k->mv_size, &key_cursor);
   5409 
   5410 	// When op is MDB_SET_RANGE, this initializes the search. Position
   5411 	// the cursor at the next key greater than or equal to the specified
   5412 	// key.
   5413 	//
   5414 	// Subsequent searches should use MDB_NEXT
   5415 	if (mdb_cursor_get(cursor, k, &v, op)) {
   5416 		// we should only do this if we're going in reverse
   5417 		if (op == MDB_SET_RANGE && order_op == MDB_PREV) {
   5418 			// if set range worked and our key exists, it should be
   5419 			// the one right before this one
   5420 			if (mdb_cursor_get(cursor, k, &v, MDB_PREV))
   5421 				return 0;
   5422 		} else {
   5423 			return 0;
   5424 		}
   5425 	}
   5426 
   5427 retry:
   5428 	retries++;
   5429 	/*
   5430 	printf("continuing from ");
   5431 	if (ndb_unpack_text_search_key(k->mv_data, k->mv_size, &search_key)) {
   5432 		ndb_print_text_search_key(&search_key);
   5433 	} else { printf("??"); }
   5434 	printf("\n");
   5435 	*/
   5436 
   5437 	make_cursor(k->mv_data, (unsigned char *)k->mv_data + k->mv_size, &key_cursor);
   5438 
   5439 	if (unlikely(!ndb_unpack_text_search_key_noteid(&key_cursor, &result->key.note_id))) {
   5440 		fprintf(stderr, "UNUSUAL: failed to unpack text search key note_id\n");
   5441 		return 0;
   5442 	}
   5443 
   5444 	// if the note id doesn't match the last result, then we stop trying
   5445 	// each search word
   5446 	if (last_result && last_result->key.note_id != result->key.note_id) {
   5447 		return 0;
   5448 	}
   5449 
   5450 	// On success, this could still be not related at all.
   5451 	// It could just be adjacent to the word. Let's check
   5452 	// if we have a matching prefix at least.
   5453 
   5454 	// Before we unpack the entire key, let's quickly
   5455 	// unpack just the string to check the prefix. We don't
   5456 	// need to unpack the entire key if the prefix doesn't
   5457 	// match
   5458 	if (!ndb_unpack_text_search_key_string(&key_cursor,
   5459 					       &result->key.str,
   5460 					       &result->key.str_len)) {
   5461 		// this should never happen
   5462 		fprintf(stderr, "UNUSUAL: failed to unpack text search key string\n");
   5463 		return 0;
   5464 	}
   5465 
   5466 	if (!ndb_prefix_matches(result, search_word)) {
   5467 		/*
   5468 		printf("result prefix '%.*s' didn't match search word '%.*s'\n",
   5469 			result->key.str_len, result->key.str,
   5470 			search_word->word_len, search_word->word);
   5471 			*/
   5472 		// we should only do this if we're going in reverse
   5473 		if (retries == 0 && op == MDB_SET_RANGE && order_op == MDB_PREV) {
   5474 			// if set range worked and our key exists, it should be
   5475 			// the one right before this one
   5476 			mdb_cursor_get(cursor, k, &v, MDB_PREV);
   5477 			goto retry;
   5478 		} else {
   5479 			return 0;
   5480 		}
   5481 	}
   5482 
   5483 	// Unpack the remaining text search key, we will need this information
   5484 	// when building up our search results.
   5485 	if (!ndb_unpack_remaining_text_search_key(&key_cursor, &result->key)) {
   5486 		// This should never happen
   5487 		fprintf(stderr, "UNUSUAL: failed to unpack text search key\n");
   5488 		return 0;
   5489 	}
   5490 
   5491 			/*
   5492 	if (last_result) {
   5493 		if (result->key.word_index < last_result->key.word_index) {
   5494 			fprintf(stderr, "skipping '%.*s' because it is before last result '%.*s'\n",
   5495 					result->key.str_len, result->key.str,
   5496 					last_result->key.str_len, last_result->key.str);
   5497 			return 0;
   5498 		}
   5499 	}
   5500 					*/
   5501 
   5502 	return 1;
   5503 }
   5504 
   5505 static void ndb_text_search_results_init(
   5506 		struct ndb_text_search_results *results) {
   5507 	results->num_results = 0;
   5508 }
   5509 
   5510 void ndb_default_text_search_config(struct ndb_text_search_config *cfg)
   5511 {
   5512 	cfg->order = NDB_ORDER_DESCENDING;
   5513 	cfg->limit = MAX_TEXT_SEARCH_RESULTS;
   5514 }
   5515 
   5516 void ndb_text_search_config_set_order(struct ndb_text_search_config *cfg,
   5517 				     enum ndb_search_order order)
   5518 {
   5519 	cfg->order = order;
   5520 }
   5521 
   5522 void ndb_text_search_config_set_limit(struct ndb_text_search_config *cfg, int limit)
   5523 {
   5524 	cfg->limit = limit;
   5525 }
   5526 
   5527 static int compare_search_words(const void *pa, const void *pb)
   5528 {
   5529 	struct ndb_word *a, *b;
   5530 
   5531 	a = (struct ndb_word *)pa;
   5532 	b = (struct ndb_word *)pb;
   5533 
   5534 	if (a->word_len == b->word_len) {
   5535 		return 0;
   5536 	} else if (a->word_len > b->word_len) {
   5537 		// biggest words should be at the front of the list,
   5538 		// so we say it's "smaller" here
   5539 		return -1;
   5540 	} else {
   5541 		return 1;
   5542 	}
   5543 }
   5544 
   5545 // Sort search words from largest to smallest. Larger words are less likely
   5546 // in the index, allowing our scan to walk fewer words at the root when
   5547 // recursively matching.
   5548 void sort_largest_to_smallest(struct ndb_search_words *words)
   5549 {
   5550 	qsort(words->words, words->num_words, sizeof(words->words[0]), compare_search_words);
   5551 }
   5552 
   5553 
   5554 int ndb_text_search_with(struct ndb_txn *txn, const char *query,
   5555 		    struct ndb_text_search_results *results,
   5556 		    struct ndb_text_search_config *config,
   5557 		    struct ndb_filter *filter)
   5558 {
   5559 	unsigned char buffer[1024], *buf;
   5560 	unsigned char saved_buf[1024], *saved;
   5561 	struct ndb_text_search_result *result, *last_result;
   5562 	struct ndb_text_search_result candidate, last_candidate;
   5563 	struct ndb_search_words search_words;
   5564 	//struct ndb_text_search_key search_key;
   5565 	struct ndb_word *search_word;
   5566 	struct ndb_note *note;
   5567 	struct cursor cur;
   5568 	uint64_t since, until, timestamp_op, *pint;
   5569 	size_t note_size;
   5570 	ndb_text_search_key_order_fn key_order_fn;
   5571 	MDB_dbi text_db;
   5572 	MDB_cursor *cursor;
   5573 	MDB_val k, v;
   5574 	int i, j, keysize, saved_size, limit;
   5575 	MDB_cursor_op op, order_op;
   5576 
   5577 	note_size = 0;
   5578 	note = 0;
   5579 	saved = NULL;
   5580 	ndb_text_search_results_init(results);
   5581 	ndb_search_words_init(&search_words);
   5582 
   5583 	until = UINT64_MAX;
   5584 	since = 0;
   5585 	limit = MAX_TEXT_SEARCH_RESULTS;
   5586 
   5587 	// until, since from filter
   5588 	if (filter != NULL) {
   5589 		if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL)))
   5590 			until = *pint;
   5591 
   5592 		if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE)))
   5593 			since = *pint;
   5594 
   5595 		if ((pint = ndb_filter_get_int(filter, NDB_FILTER_LIMIT)))
   5596 			limit = *pint;
   5597 	}
   5598 
   5599 	order_op = MDB_PREV;
   5600 	key_order_fn = ndb_make_text_search_key_high;
   5601 	timestamp_op = until;
   5602 	if (config) {
   5603 		if (config->order == NDB_ORDER_ASCENDING) {
   5604 			order_op = MDB_NEXT;
   5605 			// set the min timestamp value to since when ascending
   5606 			timestamp_op = since;
   5607 			key_order_fn = ndb_make_text_search_key_low;
   5608 		}
   5609 		limit = min(limit, config->limit);
   5610 	}
   5611 	// end search config
   5612 
   5613 	text_db = txn->lmdb->dbs[NDB_DB_NOTE_TEXT];
   5614 	make_cursor((unsigned char *)query, (unsigned char *)query + strlen(query), &cur);
   5615 
   5616 	ndb_parse_words(&cur, &search_words, ndb_parse_search_words);
   5617 	if (search_words.num_words == 0)
   5618 		return 0;
   5619 
   5620 	if ((i = mdb_cursor_open(txn->mdb_txn, text_db, &cursor))) {
   5621 		fprintf(stderr, "nd_text_search: mdb_cursor_open failed, error %d\n", i);
   5622 		return 0;
   5623 	}
   5624 
   5625 	// This should complete the query quicker because the larger words are
   5626 	// likely to have fewer entries in the search index. This is not always
   5627 	// true. Words with higher frequency (like bitcoin on nostr in 2024)
   5628 	// may be slower. TODO: Skip word recursion by leveraging a minimal
   5629 	// perfect hashmap of parsed words on a note
   5630 	sort_largest_to_smallest(&search_words);
   5631 
   5632 	// for each word, we recursively find all of the submatches
   5633 	while (results->num_results < limit) {
   5634 		last_result = NULL;
   5635 		result = &results->results[results->num_results];
   5636 
   5637 		// if we have saved, then we continue from the last root search
   5638 		// sequence
   5639 		if (saved) {
   5640 			buf = saved_buf;
   5641 			saved = NULL;
   5642 			keysize = saved_size;
   5643 
   5644 			k.mv_data = buf;
   5645 			k.mv_size = saved_size;
   5646 
   5647 			// reposition the cursor so we can continue
   5648 			if (mdb_cursor_get(cursor, &k, &v, MDB_SET_RANGE))
   5649 				break;
   5650 
   5651 			op = order_op;
   5652 		} else {
   5653 			// construct a packed fulltext search key using this
   5654 			// word. This key doesn't contain any timestamp or index
   5655 			// info, so it should range match instead of exact
   5656 			// match
   5657 			if (!key_order_fn(buffer, sizeof(buffer),
   5658 					  search_words.words[0].word_len,
   5659 					  search_words.words[0].word,
   5660 					  timestamp_op,
   5661 					  &keysize))
   5662 			{
   5663 				// word is too big to fit in 1024-sized key
   5664 				continue;
   5665 			}
   5666 
   5667 			buf = buffer;
   5668 			op = MDB_SET_RANGE;
   5669 		}
   5670 
   5671 		for (j = 0; j < search_words.num_words; j++) {
   5672 			search_word = &search_words.words[j];
   5673 
   5674 			// shouldn't happen but let's be defensive a bit
   5675 			if (search_word->word_len == 0)
   5676 				continue;
   5677 
   5678 			// if we already matched a note in this phrase, make
   5679 			// sure we're including the note id in the query
   5680 			if (last_result) {
   5681 				// we are narrowing down a search.
   5682 				// if we already have this note id, just continue
   5683 				for (i = 0; i < results->num_results; i++) {
   5684 					if (results->results[i].key.note_id == last_result->key.note_id)
   5685 						// we can't break here to
   5686 						// leave the word loop so
   5687 						// have to use a goto
   5688 						goto cont;
   5689 				}
   5690 
   5691 				if (!ndb_make_noted_text_search_key(
   5692 					buffer, sizeof(buffer),
   5693 					search_word->word_len,
   5694 					search_word->word,
   5695 					last_result->key.timestamp,
   5696 					last_result->key.note_id,
   5697 					&keysize))
   5698 				{
   5699 					continue;
   5700 				}
   5701 
   5702 				buf = buffer;
   5703 			}
   5704 
   5705 			k.mv_data = buf;
   5706 			k.mv_size = keysize;
   5707 
   5708 			// TODO: we can speed this up with the minimal perfect
   5709 			// hashmap by quickly rejecting the remaining words
   5710 			// by looking in the word hashmap on the note. This
   5711 			// would allow us to skip the recursive word lookup
   5712 			// thing
   5713 			if (!ndb_text_search_next_word(cursor, op, &k,
   5714 						       search_word,
   5715 						       last_result,
   5716 						       &candidate,
   5717 						       order_op)) {
   5718 				// we didn't find a match for this note_id
   5719 				if (j == 0)
   5720 					// if we're at one of the root words,
   5721 					// this means that there are no further
   5722 					// root word matches for any note, so
   5723 					// we know we're done
   5724 					goto done;
   5725 				else
   5726 					break;
   5727 			}
   5728 
   5729 			*result = candidate;
   5730 			op = MDB_SET_RANGE;
   5731 
   5732 			// save the first key match, since we will continue from
   5733 			// this on the next root word result
   5734 			if (j == 0) {
   5735 				if (!saved) {
   5736 					memcpy(saved_buf, k.mv_data, k.mv_size);
   5737 					saved = saved_buf;
   5738 					saved_size = k.mv_size;
   5739 				}
   5740 
   5741 				// since we will be trying to match the same
   5742 				// note_id on all subsequent word matches,
   5743 				// let's lookup this note and make sure it
   5744 				// matches the filter if we have one. If it
   5745 				// doesn't match, we can quickly skip the
   5746 				// remaining word queries
   5747 				if (filter) {
   5748 					if ((note = ndb_get_note_by_key(txn,
   5749 							result->key.note_id,
   5750 							&note_size)))
   5751 					{
   5752 						if (!ndb_filter_matches(filter, note)) {
   5753 							break;
   5754 						}
   5755 						result->note = note;
   5756 						result->note_size = note_size;
   5757 					}
   5758 				}
   5759 			}
   5760 
   5761 			result->note = note;
   5762 			result->note_size = note_size;
   5763 			last_candidate = *result;
   5764 			last_result = &last_candidate;
   5765 		}
   5766 
   5767 		// we matched all of the queries!
   5768 		if (j == search_words.num_words) {
   5769 			results->num_results++;
   5770 		}
   5771 
   5772 cont:
   5773 		;
   5774 	}
   5775 
   5776 done:
   5777 	mdb_cursor_close(cursor);
   5778 
   5779 	return 1;
   5780 }
   5781 
   5782 int ndb_text_search(struct ndb_txn *txn, const char *query,
   5783 		    struct ndb_text_search_results *results,
   5784 		    struct ndb_text_search_config *config)
   5785 {
   5786 	return ndb_text_search_with(txn, query, results, config, NULL);
   5787 }
   5788 
   5789 static void ndb_write_blocks(struct ndb_txn *txn, uint64_t note_key,
   5790 			     struct ndb_blocks *blocks)
   5791 {
   5792 	int rc;
   5793 	MDB_val key, val;
   5794 
   5795 	// make sure we're not writing the owned flag to the db
   5796 	blocks->flags &= ~NDB_BLOCK_FLAG_OWNED;
   5797 
   5798 	key.mv_data = &note_key;
   5799 	key.mv_size = sizeof(note_key);
   5800 	val.mv_data = blocks;
   5801 	val.mv_size = ndb_blocks_total_size(blocks);
   5802 	assert((val.mv_size % 8) == 0);
   5803 
   5804 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_BLOCKS], &key, &val, 0))) {
   5805 		ndb_debug("write version to note_blocks failed: %s\n",
   5806 				mdb_strerror(rc));
   5807 		return;
   5808 	}
   5809 }
   5810 
   5811 static int ndb_write_new_blocks(struct ndb_txn *txn, struct ndb_note *note,
   5812 				uint64_t note_key, unsigned char *scratch,
   5813 				size_t scratch_size)
   5814 {
   5815 	size_t content_len;
   5816 	const char *content;
   5817 	struct ndb_blocks *blocks;
   5818 
   5819 	content_len = ndb_note_content_length(note);
   5820 	content = ndb_note_content(note);
   5821 
   5822 	if (!ndb_parse_content(scratch, scratch_size, content, content_len, &blocks)) {
   5823 		//ndb_debug("failed to parse content '%.*s'\n", content_len, content);
   5824 		return 0;
   5825 	}
   5826 
   5827 	ndb_write_blocks(txn, note_key, blocks);
   5828 	return 1;
   5829 }
   5830 
   5831 
   5832 // find the last id tag in a note (e, p, etc)
   5833 static unsigned char *ndb_note_first_tag_id(struct ndb_note *note, char tag)
   5834 {
   5835 	struct ndb_iterator iter;
   5836 	struct ndb_str str;
   5837 
   5838 	// get the liked event id (last id)
   5839 	ndb_tags_iterate_start(note, &iter);
   5840 
   5841 	while (ndb_tags_iterate_next(&iter)) {
   5842 		if (iter.tag->count < 2)
   5843 			continue;
   5844 
   5845 		str = ndb_tag_str(note, iter.tag, 0);
   5846 
   5847 		// assign liked to the last e tag
   5848 		if (str.flag == NDB_PACKED_STR && str.str[0] == tag) {
   5849 			str = ndb_tag_str(note, iter.tag, 1);
   5850 			if (str.flag == NDB_PACKED_ID)
   5851 				return str.id;
   5852 		}
   5853 	}
   5854 
   5855 	return NULL;
   5856 }
   5857 
   5858 static int ndb_increment_quote_metadata(
   5859 		struct ndb_txn *txn,
   5860 		unsigned char *quoted_note_id,
   5861 		unsigned char *scratch,
   5862 		size_t scratch_size)
   5863 {
   5864 	MDB_val key, val;
   5865 	uint16_t *quotes;
   5866 	struct ndb_note_meta *meta;
   5867 	struct ndb_note_meta_entry *entry;
   5868 	int rc;
   5869 
   5870 	meta = ndb_get_note_meta(txn, quoted_note_id);
   5871 	rc = ndb_note_meta_clone_with_entry(&meta, &entry,
   5872 		NDB_NOTE_META_COUNTS,
   5873 		NULL, /* payload to match. only relevant for reactions */
   5874 		scratch,
   5875 		scratch_size);
   5876 
   5877 	switch (rc) {
   5878 	case NDB_META_CLONE_FAILED:
   5879 		return 0;
   5880 	case NDB_META_CLONE_NEW_ENTRY:
   5881 		ndb_note_meta_counts_set(entry, 0, 1, 0, 0, 0);
   5882 		break;
   5883 	case NDB_META_CLONE_EXISTING_ENTRY:
   5884 		quotes = ndb_note_meta_counts_quotes(entry);
   5885 		(*quotes)++;
   5886 		break;
   5887 	}
   5888 
   5889 	key.mv_data = quoted_note_id;
   5890 	key.mv_size = 32;
   5891 
   5892 	val.mv_data = meta;
   5893 	val.mv_size = ndb_note_meta_total_size(meta);
   5894 	assert((val.mv_size % 8) == 0);
   5895 
   5896 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
   5897 		ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
   5898 		return 0;
   5899 	}
   5900 
   5901 	return 1;
   5902 }
   5903 
   5904 /* update reply count metadata for a specific note id */
   5905 static int ndb_increment_direct_reply_metadata(
   5906 		struct ndb_txn *txn,
   5907 		unsigned char *id,
   5908 		unsigned char *scratch,
   5909 		size_t scratch_size)
   5910 {
   5911 	MDB_val key, val;
   5912 	uint16_t *direct_replies;
   5913 	struct ndb_note_meta *meta;
   5914 	struct ndb_note_meta_entry *entry;
   5915 	int rc;
   5916 
   5917 	meta = ndb_get_note_meta(txn, id);
   5918 	rc = ndb_note_meta_clone_with_entry(&meta, &entry,
   5919 		NDB_NOTE_META_COUNTS,
   5920 		NULL, /* payload to match. only relevant for reactions */
   5921 		scratch,
   5922 		scratch_size);
   5923 
   5924 	switch (rc) {
   5925 	case NDB_META_CLONE_FAILED:
   5926 		return 0;
   5927 	case NDB_META_CLONE_NEW_ENTRY:
   5928 		ndb_note_meta_counts_set(entry, 0, 0, 1, 0, 0);
   5929 		break;
   5930 	case NDB_META_CLONE_EXISTING_ENTRY:
   5931 		direct_replies = ndb_note_meta_counts_direct_replies(entry);
   5932 		(*direct_replies)++;
   5933 		break;
   5934 	}
   5935 
   5936 	key.mv_data = id;
   5937 	key.mv_size = 32;
   5938 
   5939 	val.mv_data = meta;
   5940 	val.mv_size = ndb_note_meta_total_size(meta);
   5941 	assert((val.mv_size % 8) == 0);
   5942 
   5943 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
   5944 		ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
   5945 		return 0;
   5946 	}
   5947 
   5948 	return 1;
   5949 }
   5950 
   5951 /* update reply count metadata for a specific note id */
   5952 static int ndb_increment_thread_reply_metadata(
   5953 		struct ndb_txn *txn,
   5954 		unsigned char *id,
   5955 		unsigned char *scratch,
   5956 		size_t scratch_size)
   5957 {
   5958 	MDB_val key, val;
   5959 	uint32_t *replies;
   5960 	struct ndb_note_meta *meta;
   5961 	struct ndb_note_meta_entry *entry;
   5962 	int rc;
   5963 
   5964 	meta = ndb_get_note_meta(txn, id);
   5965 	rc = ndb_note_meta_clone_with_entry(&meta, &entry,
   5966 		NDB_NOTE_META_COUNTS,
   5967 		NULL, /* payload to match. only relevant for reactions */
   5968 		scratch,
   5969 		scratch_size);
   5970 
   5971 	switch (rc) {
   5972 	case NDB_META_CLONE_FAILED:
   5973 		return 0;
   5974 	case NDB_META_CLONE_NEW_ENTRY:
   5975 		ndb_note_meta_counts_set(entry, 0, 0, 0, 1, 0);
   5976 		break;
   5977 	case NDB_META_CLONE_EXISTING_ENTRY:
   5978 		replies = ndb_note_meta_counts_thread_replies(entry);
   5979 		(*replies)++;
   5980 		break;
   5981 	}
   5982 
   5983 	key.mv_data = id;
   5984 	key.mv_size = 32;
   5985 
   5986 	val.mv_data = meta;
   5987 	val.mv_size = ndb_note_meta_total_size(meta);
   5988 	assert((val.mv_size % 8) == 0);
   5989 
   5990 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
   5991 		ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
   5992 		return 0;
   5993 	}
   5994 
   5995 	return 1;
   5996 }
   5997 
   5998 /* update reply count metadata for a specific note id */
   5999 static int ndb_increment_repost_metadata(
   6000 		struct ndb_txn *txn,
   6001 		unsigned char *id,
   6002 		unsigned char *scratch,
   6003 		size_t scratch_size)
   6004 {
   6005 	MDB_val key, val;
   6006 	uint16_t *reposts;
   6007 	struct ndb_note_meta *meta;
   6008 	struct ndb_note_meta_entry *entry;
   6009 	int rc;
   6010 
   6011 	meta = ndb_get_note_meta(txn, id);
   6012 	rc = ndb_note_meta_clone_with_entry(&meta, &entry,
   6013 		NDB_NOTE_META_COUNTS,
   6014 		NULL, /* payload to match. only relevant for reactions */
   6015 		scratch,
   6016 		scratch_size);
   6017 
   6018 	switch (rc) {
   6019 	case NDB_META_CLONE_FAILED:
   6020 		return 0;
   6021 	case NDB_META_CLONE_NEW_ENTRY:
   6022 		ndb_note_meta_counts_set(entry, 0, 0, 0, 0, 1);
   6023 		break;
   6024 	case NDB_META_CLONE_EXISTING_ENTRY:
   6025 		reposts = ndb_note_meta_counts_reposts(entry);
   6026 		(*reposts)++;
   6027 		break;
   6028 	}
   6029 
   6030 	key.mv_data = id;
   6031 	key.mv_size = 32;
   6032 
   6033 	val.mv_data = meta;
   6034 	val.mv_size = ndb_note_meta_total_size(meta);
   6035 	assert((val.mv_size % 8) == 0);
   6036 
   6037 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) {
   6038 		ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc));
   6039 		return 0;
   6040 	}
   6041 
   6042 	return 1;
   6043 }
   6044 
   6045 static void ndb_process_repost_stats(struct ndb_txn *txn, struct ndb_note *note, unsigned char *scratch, size_t scratch_size)
   6046 {
   6047 	unsigned char *reposted_note_id;
   6048 	reposted_note_id = ndb_note_first_tag_id(note, 'e');
   6049 
   6050 	/* find q tag to see if we are quoting anything */
   6051 	if (reposted_note_id) {
   6052 		ndb_increment_repost_metadata(txn, reposted_note_id, scratch, scratch_size);
   6053 	}
   6054 }
   6055 
   6056 /* process quote and reply count metadata */
   6057 static void ndb_process_note_stats(
   6058 		struct ndb_txn *txn,
   6059 		struct ndb_note *note,
   6060 		unsigned char *scratch,
   6061 		size_t scratch_size)
   6062 {
   6063 	unsigned char *quoted_note_id, *reply_id;
   6064 	struct ndb_note_reply reply;
   6065 
   6066 	reply_id = NULL;
   6067 
   6068 	/* find q tag to see if we are quoting anything */
   6069 	if ((quoted_note_id = ndb_note_first_tag_id(note, 'q'))) {
   6070 		ndb_increment_quote_metadata(txn, quoted_note_id, scratch, scratch_size);
   6071 	}
   6072 
   6073 	ndb_parse_reply(note, &reply);
   6074 	if (ndb_is_reply_to_root(&reply)) {
   6075 		reply_id = reply.root;
   6076 	} else {
   6077 		reply_id = reply.reply;
   6078 	}
   6079 
   6080 	if (reply_id) {
   6081 		ndb_increment_direct_reply_metadata(txn, reply_id, scratch, scratch_size);
   6082 	}
   6083 
   6084 	if (reply.root) {
   6085 		ndb_increment_thread_reply_metadata(txn, reply.root, scratch, scratch_size);
   6086 	}
   6087 }
   6088 
   6089 static uint64_t ndb_write_note(struct ndb_txn *txn,
   6090 			       struct ndb_writer_note *note,
   6091 			       unsigned char *scratch, size_t scratch_size,
   6092 			       uint32_t ndb_flags)
   6093 {
   6094 	int rc;
   6095 	uint64_t note_key, kind;
   6096 	struct ndb_relay_kind_key relay_key;
   6097 	MDB_dbi note_db;
   6098 	MDB_val key, val;
   6099 
   6100 	kind = note->note->kind;
   6101 
   6102 	// let's quickly sanity check if we already have this note
   6103 	if ((note_key = ndb_get_notekey_by_id(txn, note->note->id))) {
   6104 		if (ndb_relay_kind_key_init(&relay_key, note_key, kind, ndb_note_created_at(note->note), note->relay))
   6105 			ndb_write_note_relay_indexes(txn, &relay_key);
   6106 		return 0;
   6107 	}
   6108 
   6109 	// get dbs
   6110 	note_db = txn->lmdb->dbs[NDB_DB_NOTE];
   6111 
   6112 	// get new key
   6113 	note_key = ndb_get_last_key(txn->mdb_txn, note_db) + 1;
   6114 
   6115 	// write note to event store
   6116 	key.mv_data = &note_key;
   6117 	key.mv_size = sizeof(note_key);
   6118 	val.mv_data = note->note;
   6119 	val.mv_size = note->note_len;
   6120 
   6121 	if ((rc = mdb_put(txn->mdb_txn, note_db, &key, &val, 0))) {
   6122 		ndb_debug("write note to db failed: %s\n", mdb_strerror(rc));
   6123 		return 0;
   6124 	}
   6125 
   6126 	ndb_write_note_id_index(txn, note->note, note_key);
   6127 	ndb_write_note_kind_index(txn, note->note, note_key);
   6128 	ndb_write_note_tag_index(txn, note->note, note_key);
   6129 	ndb_write_note_pubkey_index(txn, note->note, note_key);
   6130 	ndb_write_note_pubkey_kind_index(txn, note->note, note_key);
   6131 
   6132 	if (ndb_relay_kind_key_init(&relay_key, note_key, kind, ndb_note_created_at(note->note), note->relay))
   6133 		ndb_write_note_relay_indexes(txn, &relay_key);
   6134 
   6135 	// only parse content and do fulltext index on text and longform notes
   6136 	if (kind == 1 || kind == 30023) {
   6137 		if (!ndb_flag_set(ndb_flags, NDB_FLAG_NO_FULLTEXT)) {
   6138 			if (!ndb_write_note_fulltext_index(txn, note->note, note_key))
   6139 				return 0;
   6140 		}
   6141 
   6142 		// write note blocks
   6143 		if (!ndb_flag_set(ndb_flags, NDB_FLAG_NO_NOTE_BLOCKS)) {
   6144 			ndb_write_new_blocks(txn, note->note, note_key, scratch, scratch_size);
   6145 		}
   6146 
   6147 		ndb_process_note_stats(txn, note->note, scratch, scratch_size);
   6148 	} else if (kind == 7 && !ndb_flag_set(ndb_flags, NDB_FLAG_NO_STATS)) {
   6149 		ndb_write_reaction_stats(txn, note->note, scratch, scratch_size);
   6150 	} else if (kind == 6 || kind == 16) {
   6151 		ndb_process_repost_stats(txn, note->note, scratch, scratch_size);
   6152 	}
   6153 
   6154 	return note_key;
   6155 }
   6156 
   6157 static void ndb_monitor_lock(struct ndb_monitor *mon) {
   6158 	pthread_mutex_lock(&mon->mutex);
   6159 }
   6160 
   6161 static void ndb_monitor_unlock(struct ndb_monitor *mon) {
   6162 	pthread_mutex_unlock(&mon->mutex);
   6163 }
   6164 
   6165 struct written_note {
   6166 	uint64_t note_id;
   6167 	struct ndb_writer_note *note;
   6168 };
   6169 
   6170 // When the data has been committed to the database, take all of the written
   6171 // notes, check them against subscriptions, and then write to the subscription
   6172 // inbox for all matching notes
   6173 static void ndb_notify_subscriptions(struct ndb_monitor *monitor,
   6174 				     struct written_note *wrote, int num_notes)
   6175 {
   6176 	int i, k;
   6177 	int pushed;
   6178 	struct written_note *written;
   6179 	struct ndb_note *note;
   6180 	struct ndb_subscription *sub;
   6181 
   6182 	ndb_monitor_lock(monitor);
   6183 
   6184 	for (i = 0; i < monitor->num_subscriptions; i++) {
   6185 		sub = &monitor->subscriptions[i];
   6186 		ndb_debug("checking subscription %d, %d notes\n", i, num_notes);
   6187 
   6188 		pushed = 0;
   6189 		for (k = 0; k < num_notes; k++) {
   6190 			written = &wrote[k];
   6191 			note = written->note->note;
   6192 
   6193 			if (ndb_filter_group_matches(&sub->group, note)) {
   6194 				ndb_debug("pushing note\n");
   6195 
   6196 				if (!prot_queue_push(&sub->inbox, &written->note_id)) {
   6197 					ndb_debug("couldn't push note to subscriber");
   6198 				} else {
   6199 					pushed++;
   6200 				}
   6201 			} else {
   6202 				ndb_debug("not pushing note\n");
   6203 			}
   6204 		}
   6205 
   6206 		// After pushing all of the matching notes, check to see if we
   6207 		// have a registered subscription callback. If so, we call it.
   6208 		// The callback needs to call ndb_poll_for_notes to pull data
   6209 		// that was just pushed to the queue in the for loop above.
   6210 		if (monitor->sub_cb != NULL && pushed > 0) {
   6211 			monitor->sub_cb(monitor->sub_cb_ctx, sub->subid);
   6212 		}
   6213 	}
   6214 
   6215 	ndb_monitor_unlock(monitor);
   6216 }
   6217 
   6218 uint64_t ndb_write_note_and_profile(
   6219 		struct ndb_txn *txn,
   6220 		struct ndb_writer_profile *profile,
   6221 		unsigned char *scratch,
   6222 		size_t scratch_size,
   6223 		uint32_t ndb_flags)
   6224 {
   6225 	uint64_t note_nkey;
   6226 
   6227 	note_nkey = ndb_write_note(txn, &profile->note, scratch, scratch_size, ndb_flags);
   6228 
   6229 	if (profile->record.builder) {
   6230 		// only write if parsing didn't fail
   6231 		ndb_write_profile(txn, profile, note_nkey);
   6232 	}
   6233 
   6234 	return note_nkey;
   6235 }
   6236 
   6237 // only to be called from the writer thread
   6238 static int ndb_write_version(struct ndb_txn *txn, uint64_t version)
   6239 {
   6240 	int rc;
   6241 	MDB_val key, val;
   6242 	uint64_t version_key;
   6243 
   6244 	version_key = NDB_META_KEY_VERSION;
   6245 
   6246 	key.mv_data = &version_key;
   6247 	key.mv_size = sizeof(version_key);
   6248 	val.mv_data = &version;
   6249 	val.mv_size = sizeof(version);
   6250 
   6251 	if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NDB_META], &key, &val, 0))) {
   6252 		ndb_debug("write version to ndb_meta failed: %s\n",
   6253 				mdb_strerror(rc));
   6254 		return 0;
   6255 	}
   6256 
   6257 	//fprintf(stderr, "writing version %" PRIu64 "\n", version);
   6258 	return 1;
   6259 }
   6260 
   6261 
   6262 static int ndb_run_migrations(struct ndb_txn *txn)
   6263 {
   6264 	int64_t version, latest_version, i;
   6265 
   6266 	latest_version = sizeof(MIGRATIONS) / sizeof(MIGRATIONS[0]);
   6267 
   6268 	if ((version = ndb_db_version(txn)) == -1) {
   6269 		ndb_debug("run_migrations: no version found, assuming new db\n");
   6270 		version = latest_version;
   6271 
   6272 		// no version found. fresh db?
   6273 		if (!ndb_write_version(txn, version)) {
   6274 			fprintf(stderr, "run_migrations: failed writing db version");
   6275 			return 0;
   6276 		}
   6277 
   6278 		return 1;
   6279 	} else {
   6280 		ndb_debug("ndb: version %" PRIu64 " found\n", version);
   6281 	}
   6282 
   6283 	if (version < latest_version)
   6284 		fprintf(stderr, "nostrdb: migrating v%d -> v%d\n",
   6285 				(int)version, (int)latest_version);
   6286 
   6287 	for (i = version; i < latest_version; i++) {
   6288 		if (!MIGRATIONS[i].fn(txn)) {
   6289 			fprintf(stderr, "run_migrations: migration v%d -> v%d failed\n", (int)i, (int)(i+1));
   6290 			return 0;
   6291 		}
   6292 
   6293 		if (!ndb_write_version(txn, i+1)) {
   6294 			fprintf(stderr, "run_migrations: failed writing db version");
   6295 			return 0;
   6296 		}
   6297 
   6298 		version = i+1;
   6299 	}
   6300 
   6301 	return 1;
   6302 }
   6303 
   6304 
   6305 static void *ndb_writer_thread(void *data)
   6306 {
   6307 	ndb_debug("started writer thread\n");
   6308 	struct ndb_writer *writer = data;
   6309 	struct ndb_writer_msg msgs[THREAD_QUEUE_BATCH], *msg;
   6310 	struct written_note written_notes[THREAD_QUEUE_BATCH];
   6311 	int i, popped, done, needs_commit, num_notes;
   6312 	uint64_t note_nkey;
   6313 	struct ndb_txn txn;
   6314 	unsigned char *scratch;
   6315 	struct ndb_relay_kind_key relay_key;
   6316 
   6317 	// 2MB scratch buffer for parsing note content
   6318 	scratch = malloc(writer->scratch_size);
   6319 	MDB_txn *mdb_txn = NULL;
   6320 	ndb_txn_from_mdb(&txn, writer->lmdb, mdb_txn);
   6321 
   6322 	done = 0;
   6323 	while (!done) {
   6324 		txn.mdb_txn = NULL;
   6325 		num_notes = 0;
   6326 		ndb_debug("writer waiting for items\n");
   6327 		popped = prot_queue_pop_all(&writer->inbox, msgs, THREAD_QUEUE_BATCH);
   6328 		ndb_debug("writer popped %d items\n", popped);
   6329 
   6330 		needs_commit = 0;
   6331 		for (i = 0 ; i < popped; i++) {
   6332 			msg = &msgs[i];
   6333 			switch (msg->type) {
   6334 			case NDB_WRITER_NOTE:
   6335 			case NDB_WRITER_NOTE_META:
   6336 			case NDB_WRITER_PROFILE:
   6337 			case NDB_WRITER_DBMETA:
   6338 			case NDB_WRITER_PROFILE_LAST_FETCH:
   6339 			case NDB_WRITER_BLOCKS:
   6340 			case NDB_WRITER_MIGRATE:
   6341 			case NDB_WRITER_NOTE_RELAY:
   6342 				needs_commit = 1;
   6343 				break;
   6344 			case NDB_WRITER_QUIT: break;
   6345 			}
   6346 		}
   6347 
   6348 		if (needs_commit && mdb_txn_begin(txn.lmdb->env, NULL, 0, (MDB_txn **)&txn.mdb_txn))
   6349 		{
   6350 			fprintf(stderr, "writer thread txn_begin failed");
   6351 			// should definitely not happen unless DB is full
   6352 			// or something ?
   6353 			continue;
   6354 		}
   6355 
   6356 		for (i = 0; i < popped; i++) {
   6357 			msg = &msgs[i];
   6358 
   6359 			switch (msg->type) {
   6360 			case NDB_WRITER_QUIT:
   6361 				// quits are handled before this
   6362 				ndb_debug("writer thread got quit message\n");
   6363 				done = 1;
   6364 				continue;
   6365 			case NDB_WRITER_PROFILE:
   6366 				note_nkey =
   6367 					ndb_write_note_and_profile(
   6368 						&txn,
   6369 						&msg->profile,
   6370 						scratch,
   6371 						writer->scratch_size,
   6372 						writer->ndb_flags);
   6373 
   6374 				if (note_nkey > 0) {
   6375 					written_notes[num_notes++] =
   6376 					(struct written_note){
   6377 						.note_id = note_nkey,
   6378 						.note = &msg->profile.note,
   6379 					};
   6380 				} else {
   6381 					ndb_debug("failed to write note\n");
   6382 				}
   6383 				break;
   6384 			case NDB_WRITER_NOTE_META:
   6385 				ndb_writer_set_note_meta(&txn, msg->note_meta.note_id, msg->note_meta.metadata);
   6386 				break;
   6387 
   6388 			case NDB_WRITER_NOTE:
   6389 				note_nkey = ndb_write_note(&txn, &msg->note,
   6390 							   scratch,
   6391 							   writer->scratch_size,
   6392 							   writer->ndb_flags);
   6393 
   6394 				if (note_nkey > 0) {
   6395 					written_notes[num_notes++] = (struct written_note){
   6396 						.note_id = note_nkey,
   6397 						.note = &msg->note,
   6398 					};
   6399 				}
   6400 				break;
   6401 			case NDB_WRITER_NOTE_RELAY:
   6402 				if (ndb_relay_kind_key_init(&relay_key,
   6403 							    msg->note_relay.note_key,
   6404 							    msg->note_relay.kind,
   6405 							    msg->note_relay.created_at,
   6406 							    msg->note_relay.relay))
   6407 				{
   6408 					ndb_write_note_relay_indexes(&txn, &relay_key);
   6409 				}
   6410 				break;
   6411 			case NDB_WRITER_DBMETA:
   6412 				ndb_write_version(&txn, msg->ndb_meta.version);
   6413 				break;
   6414 			case NDB_WRITER_BLOCKS:
   6415 				ndb_write_blocks(&txn, msg->blocks.note_key,
   6416 						       msg->blocks.blocks);
   6417 				break;
   6418 			case NDB_WRITER_MIGRATE:
   6419 				if (!ndb_run_migrations(&txn)) {
   6420 					mdb_txn_abort(txn.mdb_txn);
   6421 					goto bail;
   6422 				}
   6423 				break;
   6424 			case NDB_WRITER_PROFILE_LAST_FETCH:
   6425 				ndb_writer_last_profile_fetch(&txn,
   6426 						msg->last_fetch.pubkey,
   6427 						msg->last_fetch.fetched_at
   6428 						);
   6429 				break;
   6430 			}
   6431 		}
   6432 
   6433 		// commit writes
   6434 		if (needs_commit) {
   6435 			if (!ndb_end_query(&txn)) {
   6436 				ndb_debug("writer thread txn commit failed\n");
   6437 			} else {
   6438 				ndb_debug("notifying subscriptions, %d notes\n", num_notes);
   6439 				ndb_notify_subscriptions(writer->monitor,
   6440 							 written_notes,
   6441 							 num_notes);
   6442 				// update subscriptions
   6443 			}
   6444 		}
   6445 
   6446 		// free notes
   6447 		for (i = 0; i < popped; i++) {
   6448 			msg = &msgs[i];
   6449 			if (msg->type == NDB_WRITER_NOTE) {
   6450 				free(msg->note.note);
   6451 				if (msg->note.relay)
   6452 					free((void*)msg->note.relay);
   6453 			} else if (msg->type == NDB_WRITER_PROFILE) {
   6454 				free(msg->profile.note.note);
   6455 				ndb_profile_record_builder_free(&msg->profile.record);
   6456 			} else if (msg->type == NDB_WRITER_BLOCKS) {
   6457 				ndb_blocks_free(msg->blocks.blocks);
   6458 			} else if (msg->type == NDB_WRITER_NOTE_RELAY) {
   6459 				free((void*)msg->note_relay.relay);
   6460 			}
   6461 		}
   6462 	}
   6463 
   6464 bail:
   6465 	free(scratch);
   6466 	ndb_debug("quitting writer thread\n");
   6467 	return NULL;
   6468 }
   6469 
   6470 static void *ndb_ingester_thread(void *data)
   6471 {
   6472 	secp256k1_context *ctx;
   6473 	struct thread *thread = data;
   6474 	struct ndb_ingester *ingester = (struct ndb_ingester *)thread->ctx;
   6475 	struct ndb_lmdb *lmdb = ingester->lmdb;
   6476 	struct ndb_ingester_msg msgs[THREAD_QUEUE_BATCH], *msg;
   6477 	struct ndb_writer_msg outs[THREAD_QUEUE_BATCH], *out;
   6478 	int i, to_write, popped, done, any_event;
   6479 	MDB_txn *read_txn = NULL;
   6480 	unsigned char *scratch;
   6481 	int rc;
   6482 
   6483 	// this is used in note verification and anything else that
   6484 	// needs a temporary buffer
   6485 	scratch = malloc(ingester->scratch_size);
   6486 
   6487 	ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
   6488 	//ndb_debug("started ingester thread\n");
   6489 
   6490 	done = 0;
   6491 	while (!done) {
   6492 		to_write = 0;
   6493 		any_event = 0;
   6494 
   6495 		popped = prot_queue_pop_all(&thread->inbox, msgs, THREAD_QUEUE_BATCH);
   6496 		ndb_debug("ingester popped %d items\n", popped);
   6497 
   6498 		for (i = 0; i < popped; i++) {
   6499 			msg = &msgs[i];
   6500 			if (msg->type == NDB_INGEST_EVENT) {
   6501 				any_event = 1;
   6502 				break;
   6503 			}
   6504 		}
   6505 
   6506 		if (any_event && (rc = mdb_txn_begin(lmdb->env, NULL, MDB_RDONLY, &read_txn))) {
   6507 			// this is bad
   6508 			fprintf(stderr, "UNUSUAL ndb_ingester: mdb_txn_begin failed: '%s'\n",
   6509 					mdb_strerror(rc));
   6510 			continue;
   6511 		}
   6512 
   6513 		for (i = 0; i < popped; i++) {
   6514 			msg = &msgs[i];
   6515 			switch (msg->type) {
   6516 			case NDB_INGEST_QUIT:
   6517 				done = 1;
   6518 				break;
   6519 
   6520 			case NDB_INGEST_EVENT:
   6521 				out = &outs[to_write];
   6522 				if (ndb_ingester_process_event(ctx, ingester,
   6523 							       &msg->event, out,
   6524 							       scratch,
   6525 							       read_txn)) {
   6526 					to_write++;
   6527 				}
   6528 			}
   6529 		}
   6530 
   6531 		if (any_event)
   6532 			mdb_txn_abort(read_txn);
   6533 
   6534 		if (to_write > 0) {
   6535 			ndb_debug("pushing %d events to write queue\n", to_write);
   6536 			if (!prot_queue_push_all(ingester->writer_inbox, outs, to_write)) {
   6537 				ndb_debug("failed pushing %d events to write queue\n", to_write);
   6538 			}
   6539 		}
   6540 	}
   6541 
   6542 	ndb_debug("quitting ingester thread\n");
   6543 	secp256k1_context_destroy(ctx);
   6544 	free(scratch);
   6545 	return NULL;
   6546 }
   6547 
   6548 
   6549 static int ndb_writer_init(struct ndb_writer *writer, struct ndb_lmdb *lmdb,
   6550 			   struct ndb_monitor *monitor, uint32_t ndb_flags,
   6551 			   int scratch_size)
   6552 {
   6553 	writer->lmdb = lmdb;
   6554 	writer->monitor = monitor;
   6555 	writer->ndb_flags = ndb_flags;
   6556 	writer->scratch_size = scratch_size;
   6557 	writer->queue_buflen = sizeof(struct ndb_writer_msg) * DEFAULT_QUEUE_SIZE;
   6558 	writer->queue_buf = malloc(writer->queue_buflen);
   6559 	if (writer->queue_buf == NULL) {
   6560 		fprintf(stderr, "ndb: failed to allocate space for writer queue");
   6561 		return 0;
   6562 	}
   6563 
   6564 	// init the writer queue.
   6565 	prot_queue_init(&writer->inbox, writer->queue_buf,
   6566 			writer->queue_buflen, sizeof(struct ndb_writer_msg));
   6567 
   6568 	// spin up the writer thread
   6569 	if (THREAD_CREATE(writer->thread_id, ndb_writer_thread, writer))
   6570 	{
   6571 		fprintf(stderr, "ndb writer thread failed to create\n");
   6572 		return 0;
   6573 	}
   6574 
   6575 	return 1;
   6576 }
   6577 
   6578 // initialize the ingester queue and then spawn the thread
   6579 static int ndb_ingester_init(struct ndb_ingester *ingester,
   6580 			     struct ndb_lmdb *lmdb,
   6581 			     struct prot_queue *writer_inbox,
   6582 			     int scratch_size,
   6583 			     const struct ndb_config *config)
   6584 {
   6585 	int elem_size, num_elems;
   6586 	static struct ndb_ingester_msg quit_msg = { .type = NDB_INGEST_QUIT };
   6587 
   6588 	// TODO: configurable queue sizes
   6589 	elem_size = sizeof(struct ndb_ingester_msg);
   6590 	num_elems = DEFAULT_QUEUE_SIZE;
   6591 
   6592 	ingester->scratch_size = scratch_size;
   6593 	ingester->writer_inbox = writer_inbox;
   6594 	ingester->lmdb = lmdb;
   6595 	ingester->flags = config->flags;
   6596 	ingester->filter = config->ingest_filter;
   6597 	ingester->filter_context = config->filter_context;
   6598 
   6599 	if (!threadpool_init(&ingester->tp, config->ingester_threads,
   6600 			     elem_size, num_elems, &quit_msg, ingester,
   6601 			     ndb_ingester_thread))
   6602 	{
   6603 		fprintf(stderr, "ndb ingester threadpool failed to init\n");
   6604 		return 0;
   6605 	}
   6606 
   6607 	return 1;
   6608 }
   6609 
   6610 static int ndb_writer_destroy(struct ndb_writer *writer)
   6611 {
   6612 	struct ndb_writer_msg msg;
   6613 
   6614 	// kill thread
   6615 	msg.type = NDB_WRITER_QUIT;
   6616 	ndb_debug("writer: pushing quit message\n");
   6617 	if (!prot_queue_push(&writer->inbox, &msg)) {
   6618 		// queue is too full to push quit message. just kill it.
   6619 		ndb_debug("writer: terminating thread\n");
   6620 		THREAD_TERMINATE(writer->thread_id);
   6621 	} else {
   6622 		ndb_debug("writer: joining thread\n");
   6623 		THREAD_FINISH(writer->thread_id);
   6624 	}
   6625 
   6626 	// cleanup
   6627 	ndb_debug("writer: cleaning up protected queue\n");
   6628 	prot_queue_destroy(&writer->inbox);
   6629 
   6630 	free(writer->queue_buf);
   6631 
   6632 	return 1;
   6633 }
   6634 
   6635 static int ndb_ingester_destroy(struct ndb_ingester *ingester)
   6636 {
   6637 	threadpool_destroy(&ingester->tp);
   6638 	return 1;
   6639 }
   6640 
   6641 static int ndb_init_lmdb(const char *filename, struct ndb_lmdb *lmdb, size_t mapsize)
   6642 {
   6643 	int rc;
   6644 	MDB_txn *txn;
   6645 
   6646 	if ((rc = mdb_env_create(&lmdb->env))) {
   6647 		fprintf(stderr, "mdb_env_create failed, error %d\n", rc);
   6648 		return 0;
   6649 	}
   6650 
   6651 	if ((rc = mdb_env_set_mapsize(lmdb->env, mapsize))) {
   6652 		fprintf(stderr, "mdb_env_set_mapsize failed, error %d\n", rc);
   6653 		return 0;
   6654 	}
   6655 
   6656 	if ((rc = mdb_env_set_maxdbs(lmdb->env, NDB_DBS))) {
   6657 		fprintf(stderr, "mdb_env_set_maxdbs failed, error %d\n", rc);
   6658 		return 0;
   6659 	}
   6660 
   6661 	if ((rc = mdb_env_open(lmdb->env, filename, 0, 0664))) {
   6662 		fprintf(stderr, "mdb_env_open failed, error %d\n", rc);
   6663 		return 0;
   6664 	}
   6665 
   6666 	// Initialize DBs
   6667 	if ((rc = mdb_txn_begin(lmdb->env, NULL, 0, &txn))) {
   6668 		fprintf(stderr, "mdb_txn_begin failed, error %d\n", rc);
   6669 		return 0;
   6670 	}
   6671 
   6672 	// note flatbuffer db
   6673 	if ((rc = mdb_dbi_open(txn, "note", MDB_CREATE | MDB_INTEGERKEY, &lmdb->dbs[NDB_DB_NOTE]))) {
   6674 		fprintf(stderr, "mdb_dbi_open event failed, error %d\n", rc);
   6675 		return 0;
   6676 	}
   6677 
   6678 	// note metadata db
   6679 	if ((rc = mdb_dbi_open(txn, "meta", MDB_CREATE, &lmdb->dbs[NDB_DB_META]))) {
   6680 		fprintf(stderr, "mdb_dbi_open meta failed, error %d\n", rc);
   6681 		return 0;
   6682 	}
   6683 
   6684 	// profile flatbuffer db
   6685 	if ((rc = mdb_dbi_open(txn, "profile", MDB_CREATE | MDB_INTEGERKEY, &lmdb->dbs[NDB_DB_PROFILE]))) {
   6686 		fprintf(stderr, "mdb_dbi_open profile failed, error %d\n", rc);
   6687 		return 0;
   6688 	}
   6689 
   6690 	// profile search db
   6691 	if ((rc = mdb_dbi_open(txn, "profile_search", MDB_CREATE, &lmdb->dbs[NDB_DB_PROFILE_SEARCH]))) {
   6692 		fprintf(stderr, "mdb_dbi_open profile_search failed, error %d\n", rc);
   6693 		return 0;
   6694 	}
   6695 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_PROFILE_SEARCH], ndb_search_key_cmp);
   6696 
   6697 	// ndb metadata (db version, etc)
   6698 	if ((rc = mdb_dbi_open(txn, "ndb_meta", MDB_CREATE | MDB_INTEGERKEY, &lmdb->dbs[NDB_DB_NDB_META]))) {
   6699 		fprintf(stderr, "mdb_dbi_open ndb_meta failed, error %d\n", rc);
   6700 		return 0;
   6701 	}
   6702 
   6703 	// profile last fetches
   6704 	if ((rc = mdb_dbi_open(txn, "profile_last_fetch", MDB_CREATE, &lmdb->dbs[NDB_DB_PROFILE_LAST_FETCH]))) {
   6705 		fprintf(stderr, "mdb_dbi_open profile last fetch, error %d\n", rc);
   6706 		return 0;
   6707 	}
   6708 
   6709 	// relay kind index. maps <relay_url><kind><created><note_id> primary keys to relay records
   6710 	// see ndb_relay_kind_cmp function for more details on the key format
   6711 	if ((rc = mdb_dbi_open(txn, "relay_kind", MDB_CREATE, &lmdb->dbs[NDB_DB_NOTE_RELAY_KIND]))) {
   6712 		fprintf(stderr, "mdb_dbi_open profile last fetch, error %d\n", rc);
   6713 		return 0;
   6714 	}
   6715 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_RELAY_KIND], ndb_relay_kind_cmp);
   6716 
   6717 	// note_id -> relay index
   6718 	if ((rc = mdb_dbi_open(txn, "note_relays", MDB_CREATE | MDB_DUPSORT, &lmdb->dbs[NDB_DB_NOTE_RELAYS]))) {
   6719 		fprintf(stderr, "mdb_dbi_open profile last fetch, error %d\n", rc);
   6720 		return 0;
   6721 	}
   6722 
   6723 	// id+ts index flags
   6724 	unsigned int tsid_flags = MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED;
   6725 
   6726 	// index dbs
   6727 	if ((rc = mdb_dbi_open(txn, "note_id", tsid_flags, &lmdb->dbs[NDB_DB_NOTE_ID]))) {
   6728 		fprintf(stderr, "mdb_dbi_open id failed: %s\n", mdb_strerror(rc));
   6729 		return 0;
   6730 	}
   6731 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_ID], ndb_tsid_compare);
   6732 
   6733 	if ((rc = mdb_dbi_open(txn, "profile_pk", tsid_flags, &lmdb->dbs[NDB_DB_PROFILE_PK]))) {
   6734 		fprintf(stderr, "mdb_dbi_open profile_pk failed: %s\n", mdb_strerror(rc));
   6735 		return 0;
   6736 	}
   6737 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_PROFILE_PK], ndb_tsid_compare);
   6738 
   6739 	if ((rc = mdb_dbi_open(txn, "note_kind",
   6740 			       MDB_CREATE | MDB_DUPSORT | MDB_INTEGERDUP | MDB_DUPFIXED,
   6741 			       &lmdb->dbs[NDB_DB_NOTE_KIND]))) {
   6742 		fprintf(stderr, "mdb_dbi_open note_kind failed: %s\n", mdb_strerror(rc));
   6743 		return 0;
   6744 	}
   6745 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_KIND], ndb_u64_ts_compare);
   6746 
   6747 	if ((rc = mdb_dbi_open(txn, "note_pubkey",
   6748 			       MDB_CREATE | MDB_DUPSORT | MDB_INTEGERDUP | MDB_DUPFIXED,
   6749 			       &lmdb->dbs[NDB_DB_NOTE_PUBKEY]))) {
   6750 		fprintf(stderr, "mdb_dbi_open note_pubkey failed: %s\n", mdb_strerror(rc));
   6751 		return 0;
   6752 	}
   6753 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_PUBKEY], ndb_tsid_compare);
   6754 
   6755 	if ((rc = mdb_dbi_open(txn, "note_pubkey_kind",
   6756 			       MDB_CREATE | MDB_DUPSORT | MDB_INTEGERDUP | MDB_DUPFIXED,
   6757 			       &lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND]))) {
   6758 		fprintf(stderr, "mdb_dbi_open note_pubkey_kind failed: %s\n", mdb_strerror(rc));
   6759 		return 0;
   6760 	}
   6761 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND], ndb_id_u64_ts_compare);
   6762 
   6763 	if ((rc = mdb_dbi_open(txn, "note_text", MDB_CREATE | MDB_DUPSORT,
   6764 			       &lmdb->dbs[NDB_DB_NOTE_TEXT]))) {
   6765 		fprintf(stderr, "mdb_dbi_open note_text failed: %s\n", mdb_strerror(rc));
   6766 		return 0;
   6767 	}
   6768 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_TEXT], ndb_text_search_key_compare);
   6769 
   6770 	if ((rc = mdb_dbi_open(txn, "note_blocks", MDB_CREATE | MDB_INTEGERKEY,
   6771 			       &lmdb->dbs[NDB_DB_NOTE_BLOCKS]))) {
   6772 		fprintf(stderr, "mdb_dbi_open note_blocks failed: %s\n", mdb_strerror(rc));
   6773 		return 0;
   6774 	}
   6775 
   6776 	if ((rc = mdb_dbi_open(txn, "note_tags", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED,
   6777 			       &lmdb->dbs[NDB_DB_NOTE_TAGS]))) {
   6778 		fprintf(stderr, "mdb_dbi_open note_tags failed: %s\n", mdb_strerror(rc));
   6779 		return 0;
   6780 	}
   6781 	mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_TAGS], ndb_tag_key_compare);
   6782 
   6783 	// Commit the transaction
   6784 	if ((rc = mdb_txn_commit(txn))) {
   6785 		fprintf(stderr, "mdb_txn_commit failed, error %d\n", rc);
   6786 		return 0;
   6787 	}
   6788 
   6789 	return 1;
   6790 }
   6791 
   6792 static int ndb_queue_write_version(struct ndb *ndb, uint64_t version)
   6793 {
   6794 	struct ndb_writer_msg msg;
   6795 	msg.type = NDB_WRITER_DBMETA;
   6796 	msg.ndb_meta.version = version;
   6797 	return ndb_writer_queue_msg(&ndb->writer, &msg);
   6798 }
   6799 
   6800 static void ndb_monitor_init(struct ndb_monitor *monitor, ndb_sub_fn cb,
   6801 			     void *sub_cb_ctx)
   6802 {
   6803 	monitor->num_subscriptions = 0;
   6804 	monitor->sub_cb = cb;
   6805 	monitor->sub_cb_ctx = sub_cb_ctx;
   6806 	pthread_mutex_init(&monitor->mutex, NULL);
   6807 }
   6808 
   6809 void ndb_filter_group_destroy(struct ndb_filter_group *group)
   6810 {
   6811 	struct ndb_filter *filter;
   6812 	int i;
   6813 	for (i = 0; i < group->num_filters; i++) {
   6814 		filter = &group->filters[i];
   6815 		ndb_filter_destroy(filter);
   6816 	}
   6817 }
   6818 
   6819 static void ndb_subscription_destroy(struct ndb_subscription *sub)
   6820 {
   6821 	ndb_filter_group_destroy(&sub->group);
   6822 	// this was malloc'd inside ndb_subscribe
   6823 	free(sub->inbox.buf);
   6824 	prot_queue_destroy(&sub->inbox);
   6825 	sub->subid = 0;
   6826 }
   6827 
   6828 static void ndb_monitor_destroy(struct ndb_monitor *monitor)
   6829 {
   6830 	int i;
   6831 
   6832 	ndb_monitor_lock(monitor);
   6833 
   6834 	for (i = 0; i < monitor->num_subscriptions; i++) {
   6835 		ndb_subscription_destroy(&monitor->subscriptions[i]);
   6836 	}
   6837 
   6838 	monitor->num_subscriptions = 0;
   6839 
   6840 	ndb_monitor_unlock(monitor);
   6841 
   6842 	pthread_mutex_destroy(&monitor->mutex);
   6843 }
   6844 
   6845 int ndb_init(struct ndb **pndb, const char *filename, const struct ndb_config *config)
   6846 {
   6847 	struct ndb *ndb;
   6848 	//MDB_dbi ind_id; // TODO: ind_pk, etc
   6849 
   6850 	ndb = *pndb = calloc(1, sizeof(struct ndb));
   6851 	ndb->flags = config->flags;
   6852 
   6853 	if (ndb == NULL) {
   6854 		fprintf(stderr, "ndb_init: malloc failed\n");
   6855 		return 0;
   6856 	}
   6857 
   6858 	if (!ndb_init_lmdb(filename, &ndb->lmdb, config->mapsize))
   6859 		return 0;
   6860 
   6861 	ndb_monitor_init(&ndb->monitor, config->sub_cb, config->sub_cb_ctx);
   6862 
   6863 	if (!ndb_writer_init(&ndb->writer, &ndb->lmdb, &ndb->monitor, ndb->flags,
   6864 			     config->writer_scratch_buffer_size)) {
   6865 		fprintf(stderr, "ndb_writer_init failed\n");
   6866 		return 0;
   6867 	}
   6868 
   6869 	if (!ndb_ingester_init(&ndb->ingester, &ndb->lmdb, &ndb->writer.inbox,
   6870 			       config->writer_scratch_buffer_size, config)) {
   6871 		fprintf(stderr, "failed to initialize %d ingester thread(s)\n",
   6872 				config->ingester_threads);
   6873 		return 0;
   6874 	}
   6875 
   6876 	if (!ndb_flag_set(config->flags, NDB_FLAG_NOMIGRATE)) {
   6877 		struct ndb_writer_msg msg = { .type = NDB_WRITER_MIGRATE };
   6878 		ndb_writer_queue_msg(&ndb->writer, &msg);
   6879 	}
   6880 
   6881 	// Initialize LMDB environment and spin up threads
   6882 	return 1;
   6883 }
   6884 
   6885 void ndb_destroy(struct ndb *ndb)
   6886 {
   6887 	if (ndb == NULL)
   6888 		return;
   6889 
   6890 	// ingester depends on writer and must be destroyed first
   6891 	ndb_debug("destroying ingester\n");
   6892 	ndb_ingester_destroy(&ndb->ingester);
   6893 	ndb_debug("destroying writer\n");
   6894 	ndb_writer_destroy(&ndb->writer);
   6895 	ndb_debug("destroying monitor\n");
   6896 	ndb_monitor_destroy(&ndb->monitor);
   6897 
   6898 	ndb_debug("closing env\n");
   6899 	mdb_env_close(ndb->lmdb.env);
   6900 
   6901 	ndb_debug("ndb destroyed\n");
   6902 	free(ndb);
   6903 }
   6904 
   6905 
   6906 // Process a nostr event from a client
   6907 //
   6908 // ie: ["EVENT", {"content":"..."} ...]
   6909 //
   6910 // The client-sent variation of ndb_process_event
   6911 int ndb_process_client_event(struct ndb *ndb, const char *json, int len)
   6912 {
   6913 	struct ndb_ingest_meta meta;
   6914 	ndb_ingest_meta_init(&meta, 1, NULL);
   6915 
   6916 	return ndb_ingest_event(&ndb->ingester, json, len, &meta);
   6917 }
   6918 
   6919 // Process anostr event from a relay,
   6920 //
   6921 // ie: ["EVENT", "subid", {"content":"..."}...]
   6922 //
   6923 // This function returns as soon as possible, first copying the passed
   6924 // json and then queueing it up for processing. Worker threads then take
   6925 // the json and process it.
   6926 //
   6927 // Processing:
   6928 //
   6929 // 1. The event is parsed into ndb_notes and the signature is validated
   6930 // 2. A quick lookup is made on the database to see if we already have
   6931 //    the note id, if we do we don't need to waste time on json parsing
   6932 //    or note validation.
   6933 // 3. Once validation is done we pass it to the writer queue for writing
   6934 //    to LMDB.
   6935 //
   6936 int ndb_process_event(struct ndb *ndb, const char *json, int json_len)
   6937 {
   6938 	struct ndb_ingest_meta meta;
   6939 	ndb_ingest_meta_init(&meta, 0, NULL);
   6940 
   6941 	return ndb_ingest_event(&ndb->ingester, json, json_len, &meta);
   6942 }
   6943 
   6944 int ndb_process_event_with(struct ndb *ndb, const char *json, int json_len,
   6945 			   struct ndb_ingest_meta *meta)
   6946 {
   6947 	return ndb_ingest_event(&ndb->ingester, json, json_len, meta);
   6948 }
   6949 
   6950 int _ndb_process_events(struct ndb *ndb, const char *ldjson, size_t json_len,
   6951 			struct ndb_ingest_meta *meta)
   6952 {
   6953 	const char *start, *end, *very_end;
   6954 	start = ldjson;
   6955 	end = start + json_len;
   6956 	very_end = ldjson + json_len;
   6957 #if DEBUG
   6958 	int processed = 0;
   6959 #endif
   6960 
   6961 	while ((end = fast_strchr(start, '\n', very_end - start))) {
   6962 		//printf("processing '%.*s'\n", (int)(end-start), start);
   6963 		if (!ndb_process_event_with(ndb, start, end - start, meta)) {
   6964 			ndb_debug("ndb_process_client_event failed\n");
   6965 			return 0;
   6966 		}
   6967 		start = end + 1;
   6968 #if DEBUG
   6969 		processed++;
   6970 #endif
   6971 	}
   6972 
   6973 #if DEBUG
   6974 	ndb_debug("ndb_process_events: processed %d events\n", processed);
   6975 #endif
   6976 
   6977 	return 1;
   6978 }
   6979 
   6980 #ifndef _WIN32
   6981 // TODO: windows
   6982 int ndb_process_events_stream(struct ndb *ndb, FILE* fp)
   6983 {
   6984 	char *line = NULL;
   6985 	size_t len = 0;
   6986 	ssize_t nread;
   6987 
   6988 	while ((nread = getline(&line, &len, fp)) != -1) {
   6989 		if (line == NULL)
   6990 			break;
   6991 		ndb_process_event(ndb, line, len);
   6992 	}
   6993 
   6994 	if (line)
   6995 		free(line);
   6996 
   6997 	return 1;
   6998 }
   6999 #endif
   7000 
   7001 int ndb_process_events_with(struct ndb *ndb, const char *ldjson, size_t json_len,
   7002 			    struct ndb_ingest_meta *meta)
   7003 {
   7004 	return _ndb_process_events(ndb, ldjson, json_len, meta);
   7005 }
   7006 
   7007 int ndb_process_client_events(struct ndb *ndb, const char *ldjson, size_t json_len)
   7008 {
   7009 	struct ndb_ingest_meta meta;
   7010 	ndb_ingest_meta_init(&meta, 1, NULL);
   7011 
   7012 	return _ndb_process_events(ndb, ldjson, json_len, &meta);
   7013 }
   7014 
   7015 int ndb_process_events(struct ndb *ndb, const char *ldjson, size_t json_len)
   7016 {
   7017 	struct ndb_ingest_meta meta;
   7018 	ndb_ingest_meta_init(&meta, 0, NULL);
   7019 
   7020 	return _ndb_process_events(ndb, ldjson, json_len, &meta);
   7021 }
   7022 
   7023 static inline int cursor_push_tag(struct cursor *cur, struct ndb_tag *tag)
   7024 {
   7025 	return cursor_push_u16(cur, tag->count);
   7026 }
   7027 
   7028 int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf,
   7029 		     size_t bufsize)
   7030 {
   7031 	struct ndb_note *note;
   7032 	int half, size, str_indices_size;
   7033 
   7034 	// come on bruh
   7035 	if (bufsize < sizeof(struct ndb_note) * 2)
   7036 		return 0;
   7037 
   7038 	str_indices_size = bufsize / 32;
   7039 	size = bufsize - str_indices_size;
   7040 	half = size / 2;
   7041 
   7042 	//debug("size %d half %d str_indices %d\n", size, half, str_indices_size);
   7043 
   7044 	// make a safe cursor of our available memory
   7045 	make_cursor(buf, buf + bufsize, &builder->mem);
   7046 
   7047 	note = builder->note = (struct ndb_note *)buf;
   7048 
   7049 	// take slices of the memory into subcursors
   7050 	if (!(cursor_slice(&builder->mem, &builder->note_cur, half) &&
   7051 	      cursor_slice(&builder->mem, &builder->strings, half) &&
   7052 	      cursor_slice(&builder->mem, &builder->str_indices, str_indices_size))) {
   7053 		return 0;
   7054 	}
   7055 
   7056 	memset(note, 0, sizeof(*note));
   7057 	builder->note_cur.p += sizeof(*note);
   7058 
   7059 	note->strings = builder->strings.start - buf;
   7060 	note->version = 1;
   7061 
   7062 	return 1;
   7063 }
   7064 
   7065 
   7066 
   7067 static inline int ndb_json_parser_init(struct ndb_json_parser *p,
   7068 				       const char *json, int json_len,
   7069 				       unsigned char *buf, int bufsize)
   7070 {
   7071 	int half = bufsize / 2;
   7072 
   7073 	unsigned char *tok_start = buf + half;
   7074 	unsigned char *tok_end = buf + bufsize;
   7075 
   7076 	p->toks = (jsmntok_t*)tok_start;
   7077 	p->toks_end = (jsmntok_t*)tok_end;
   7078 	p->num_tokens = 0;
   7079 	p->json = json;
   7080 	p->json_len = json_len;
   7081 
   7082 	// ndb_builder gets the first half of the buffer, and jsmn gets the
   7083 	// second half. I like this way of alloating memory (without actually
   7084 	// dynamically allocating memory). You get one big chunk upfront and
   7085 	// then submodules can recursively subdivide it. Maybe you could do
   7086 	// something even more clever like golden-ratio style subdivision where
   7087 	// the more important stuff gets a larger chunk and then it spirals
   7088 	// downward into smaller chunks. Thanks for coming to my TED talk.
   7089 
   7090 	if (!ndb_builder_init(&p->builder, buf, half))
   7091 		return 0;
   7092 
   7093 	jsmn_init(&p->json_parser);
   7094 
   7095 	return 1;
   7096 }
   7097 
   7098 static inline int ndb_json_parser_parse(struct ndb_json_parser *p,
   7099 					struct ndb_id_cb *cb)
   7100 {
   7101 	jsmntok_t *tok;
   7102 	int cap = ((unsigned char *)p->toks_end - (unsigned char*)p->toks)/sizeof(*p->toks);
   7103 	int res =
   7104 		jsmn_parse(&p->json_parser, p->json, p->json_len, p->toks, cap, cb != NULL);
   7105 
   7106 	// got an ID!
   7107 	if (res == -42) {
   7108 		tok = &p->toks[p->json_parser.toknext-1];
   7109 
   7110 		switch (cb->fn(cb->data, p->json + tok->start)) {
   7111 		case NDB_IDRES_CONT:
   7112 			res = jsmn_parse(&p->json_parser, p->json, p->json_len,
   7113 					 p->toks, cap, 0);
   7114 			break;
   7115 		case NDB_IDRES_STOP:
   7116 			return -42;
   7117 		}
   7118 	} else if (res == 0) {
   7119 		return 0;
   7120 	}
   7121 
   7122 	p->num_tokens = res;
   7123 	p->i = 0;
   7124 
   7125 	return 1;
   7126 }
   7127 
   7128 static inline int toksize(jsmntok_t *tok)
   7129 {
   7130 	return tok->end - tok->start;
   7131 }
   7132 
   7133 
   7134 
   7135 static int cursor_push_unescaped_char(struct cursor *cur, char c1, char c2)
   7136 {
   7137 	switch (c2) {
   7138 	case 't':  return cursor_push_byte(cur, '\t');
   7139 	case 'n':  return cursor_push_byte(cur, '\n');
   7140 	case 'r':  return cursor_push_byte(cur, '\r');
   7141 	case 'b':  return cursor_push_byte(cur, '\b');
   7142 	case 'f':  return cursor_push_byte(cur, '\f');
   7143 	case '\\': return cursor_push_byte(cur, '\\');
   7144 	case '/':  return cursor_push_byte(cur, '/');
   7145 	case '"':  return cursor_push_byte(cur, '"');
   7146 	case 'u':
   7147 		// these aren't handled yet
   7148 		return 0;
   7149 	default:
   7150 		return cursor_push_byte(cur, c1) && cursor_push_byte(cur, c2);
   7151 	}
   7152 }
   7153 
   7154 static int cursor_push_escaped_char(struct cursor *cur, char c)
   7155 {
   7156         switch (c) {
   7157         case '"':  return cursor_push_str(cur, "\\\"");
   7158         case '\\': return cursor_push_str(cur, "\\\\");
   7159         case '\b': return cursor_push_str(cur, "\\b");
   7160         case '\f': return cursor_push_str(cur, "\\f");
   7161         case '\n': return cursor_push_str(cur, "\\n");
   7162         case '\r': return cursor_push_str(cur, "\\r");
   7163         case '\t': return cursor_push_str(cur, "\\t");
   7164         // TODO: \u hex hex hex hex
   7165         }
   7166         return cursor_push_byte(cur, c);
   7167 }
   7168 
   7169 static int cursor_push_hex_str(struct cursor *cur, unsigned char *buf, int len)
   7170 {
   7171 	int i;
   7172 
   7173 	if (len % 2 != 0)
   7174 		return 0;
   7175 
   7176         if (!cursor_push_byte(cur, '"'))
   7177                 return 0;
   7178 
   7179 	for (i = 0; i < len; i++) {
   7180 		unsigned int c = ((const unsigned char *)buf)[i];
   7181 		if (!cursor_push_byte(cur, hexchar(c >> 4)))
   7182 			return 0;
   7183 		if (!cursor_push_byte(cur, hexchar(c & 0xF)))
   7184 			return 0;
   7185 	}
   7186 
   7187         if (!cursor_push_byte(cur, '"'))
   7188                 return 0;
   7189 
   7190 	return 1;
   7191 }
   7192 
   7193 static int cursor_push_jsonstr(struct cursor *cur, const char *str)
   7194 {
   7195 	int i;
   7196         int len;
   7197 
   7198 	len = strlen(str);
   7199 
   7200         if (!cursor_push_byte(cur, '"'))
   7201                 return 0;
   7202 
   7203         for (i = 0; i < len; i++) {
   7204                 if (!cursor_push_escaped_char(cur, str[i]))
   7205                         return 0;
   7206         }
   7207 
   7208         if (!cursor_push_byte(cur, '"'))
   7209                 return 0;
   7210 
   7211         return 1;
   7212 }
   7213 
   7214 
   7215 static inline int cursor_push_json_tag_str(struct cursor *cur, struct ndb_str str)
   7216 {
   7217 	if (str.flag == NDB_PACKED_ID)
   7218 		return cursor_push_hex_str(cur, str.id, 32);
   7219 
   7220 	return cursor_push_jsonstr(cur, str.str);
   7221 }
   7222 
   7223 static int cursor_push_json_tag(struct cursor *cur, struct ndb_note *note,
   7224 				struct ndb_tag *tag)
   7225 {
   7226         int i;
   7227 
   7228         if (!cursor_push_byte(cur, '['))
   7229                 return 0;
   7230 
   7231         for (i = 0; i < tag->count; i++) {
   7232                 if (!cursor_push_json_tag_str(cur, ndb_tag_str(note, tag, i)))
   7233                         return 0;
   7234                 if (i != tag->count-1 && !cursor_push_byte(cur, ','))
   7235 			return 0;
   7236         }
   7237 
   7238         return cursor_push_byte(cur, ']');
   7239 }
   7240 
   7241 static int cursor_push_json_tags(struct cursor *cur, struct ndb_note *note)
   7242 {
   7243 	int i;
   7244 	struct ndb_iterator iter, *it = &iter;
   7245 	ndb_tags_iterate_start(note, it);
   7246 
   7247         if (!cursor_push_byte(cur, '['))
   7248                 return 0;
   7249 
   7250 	i = 0;
   7251 	while (ndb_tags_iterate_next(it)) {
   7252 		if (!cursor_push_json_tag(cur, note, it->tag))
   7253 			return 0;
   7254                 if (i != note->tags.count-1 && !cursor_push_str(cur, ","))
   7255 			return 0;
   7256 		i++;
   7257 	}
   7258 
   7259         if (!cursor_push_byte(cur, ']'))
   7260                 return 0;
   7261 
   7262 	return 1;
   7263 }
   7264 
   7265 static int ndb_event_commitment(struct ndb_note *ev, unsigned char *buf, int buflen)
   7266 {
   7267 	char timebuf[16] = {0};
   7268 	char kindbuf[16] = {0};
   7269 	char pubkey[65];
   7270 	struct cursor cur;
   7271 	int ok;
   7272 
   7273 	if (!hex_encode(ev->pubkey, sizeof(ev->pubkey), pubkey))
   7274 		return 0;
   7275 
   7276 	make_cursor(buf, buf + buflen, &cur);
   7277 
   7278 	// TODO: update in 2106 ...
   7279 	snprintf(timebuf, sizeof(timebuf), "%d", (uint32_t)ev->created_at);
   7280 	snprintf(kindbuf, sizeof(kindbuf), "%d", ev->kind);
   7281 
   7282 	ok =
   7283 		cursor_push_str(&cur, "[0,\"") &&
   7284 		cursor_push_str(&cur, pubkey) &&
   7285 		cursor_push_str(&cur, "\",") &&
   7286 		cursor_push_str(&cur, timebuf) &&
   7287 		cursor_push_str(&cur, ",") &&
   7288 		cursor_push_str(&cur, kindbuf) &&
   7289 		cursor_push_str(&cur, ",") &&
   7290 		cursor_push_json_tags(&cur, ev) &&
   7291 		cursor_push_str(&cur, ",") &&
   7292 		cursor_push_jsonstr(&cur, ndb_note_str(ev, &ev->content).str) &&
   7293 		cursor_push_str(&cur, "]");
   7294 
   7295 	if (!ok)
   7296 		return 0;
   7297 
   7298 	return cur.p - cur.start;
   7299 }
   7300 
   7301 static int cursor_push_hex(struct cursor *c, unsigned char *bytes, int len)
   7302 {
   7303 	int i;
   7304 	unsigned char chr;
   7305 	if (c->p + (len * 2) >= c->end)
   7306 		return 0;
   7307 
   7308 	for (i = 0; i < len; i++) {
   7309 		chr = bytes[i];
   7310 
   7311 		*(c->p++) = hexchar(chr >> 4);
   7312 		*(c->p++) = hexchar(chr & 0xF);
   7313 	}
   7314 
   7315 	return 1;
   7316 }
   7317 
   7318 static int cursor_push_int_str(struct cursor *c, uint64_t num)
   7319 {
   7320 	char timebuf[16] = {0};
   7321 	snprintf(timebuf, sizeof(timebuf), "%" PRIu64, num);
   7322 	return cursor_push_str(c, timebuf);
   7323 }
   7324 
   7325 int ndb_note_json(struct ndb_note *note, char *buf, int buflen)
   7326 {
   7327 	struct cursor cur, *c = &cur;
   7328 
   7329 	make_cursor((unsigned char *)buf, (unsigned char*)buf + buflen, &cur);
   7330 
   7331 	int ok = cursor_push_str(c, "{\"id\":\"") &&
   7332 	       cursor_push_hex(c, ndb_note_id(note), 32) &&
   7333 	       cursor_push_str(c, "\",\"pubkey\":\"") &&
   7334 	       cursor_push_hex(c, ndb_note_pubkey(note), 32) &&
   7335 	       cursor_push_str(c, "\",\"created_at\":") &&
   7336 	       cursor_push_int_str(c, ndb_note_created_at(note)) &&
   7337 	       cursor_push_str(c, ",\"kind\":") &&
   7338 	       cursor_push_int_str(c, ndb_note_kind(note)) &&
   7339 	       cursor_push_str(c, ",\"tags\":") &&
   7340 	       cursor_push_json_tags(c, note) &&
   7341 	       cursor_push_str(c, ",\"content\":") &&
   7342 	       cursor_push_jsonstr(c, ndb_note_content(note)) &&
   7343 	       cursor_push_str(c, ",\"sig\":\"") &&
   7344 	       cursor_push_hex(c, ndb_note_sig(note), 64) &&
   7345 	       cursor_push_c_str(c, "\"}");
   7346 
   7347 	if (!ok) {
   7348 		return 0;
   7349 	}
   7350 
   7351 	return cur.p - cur.start;
   7352 }
   7353 
   7354 static int cursor_push_json_elem_array(struct cursor *cur,
   7355 				       const struct ndb_filter *filter,
   7356 				       struct ndb_filter_elements *elems)
   7357 {
   7358 	int i;
   7359 	unsigned char *id;
   7360 	const char *str;
   7361 	uint64_t val;
   7362 
   7363 	if (!cursor_push_byte(cur, '['))
   7364 		return 0;
   7365 
   7366 	for (i = 0; i < elems->count; i++) {
   7367 
   7368 		switch (elems->field.elem_type)  {
   7369 		case NDB_ELEMENT_CUSTOM:
   7370 			// can't serialize custom functions
   7371 			break;
   7372 		case NDB_ELEMENT_STRING:
   7373 			str = ndb_filter_get_string_element(filter, elems, i);
   7374 			if (!cursor_push_jsonstr(cur, str))
   7375 				return 0;
   7376 			break;
   7377 		case NDB_ELEMENT_ID:
   7378 			id = ndb_filter_get_id_element(filter, elems, i);
   7379 			if (!cursor_push_hex_str(cur, id, 32))
   7380 				return 0;
   7381 			break;
   7382 		case NDB_ELEMENT_INT:
   7383 			val = ndb_filter_get_int_element(elems, i);
   7384 			if (!cursor_push_int_str(cur, val))
   7385 				return 0;
   7386 			break;
   7387 		case NDB_ELEMENT_UNKNOWN:
   7388 			ndb_debug("unknown element in cursor_push_json_elem_array");
   7389 			return 0;
   7390 		}
   7391 
   7392 		if (i != elems->count-1) {
   7393 			if (!cursor_push_byte(cur, ','))
   7394 				return 0;
   7395 		}
   7396 	}
   7397 
   7398 	if (!cursor_push_str(cur, "]"))
   7399 		return 0;
   7400 
   7401 	return 1;
   7402 }
   7403 
   7404 int ndb_filter_json(const struct ndb_filter *filter, char *buf, int buflen)
   7405 {
   7406 	const char *str;
   7407 	struct cursor cur, *c = &cur;
   7408 	struct ndb_filter_elements *elems;
   7409 	int i;
   7410 
   7411 	if (!filter->finalized) {
   7412 		ndb_debug("filter not finalized in ndb_filter_json\n");
   7413 		return 0;
   7414 	}
   7415 
   7416 	make_cursor((unsigned char *)buf, (unsigned char*)buf + buflen, c);
   7417 
   7418 	if (!cursor_push_str(c, "{"))
   7419 		return 0;
   7420 
   7421 	for (i = 0; i < filter->num_elements; i++) {
   7422 		elems = ndb_filter_get_elements(filter, i);
   7423 		switch (elems->field.type) {
   7424 		case NDB_FILTER_CUSTOM:
   7425 			// nothing to encode these as
   7426 			break;
   7427 		case NDB_FILTER_IDS:
   7428 			if (!cursor_push_str(c, "\"ids\":"))
   7429 				return 0;
   7430 			if (!cursor_push_json_elem_array(c, filter, elems))
   7431 				return 0;
   7432 			break;
   7433 		case NDB_FILTER_SEARCH:
   7434 			if (!cursor_push_str(c, "\"search\":"))
   7435 				return 0;
   7436 			if (!(str = ndb_filter_get_string_element(filter, elems, 0)))
   7437 				return 0;
   7438 			if (!cursor_push_jsonstr(c, str))
   7439 				return 0;
   7440 			break;
   7441 		case NDB_FILTER_AUTHORS:
   7442 			if (!cursor_push_str(c, "\"authors\":"))
   7443 				return 0;
   7444 			if (!cursor_push_json_elem_array(c, filter, elems))
   7445 				return 0;
   7446 			break;
   7447 		case NDB_FILTER_KINDS:
   7448 			if (!cursor_push_str(c, "\"kinds\":"))
   7449 				return 0;
   7450 			if (!cursor_push_json_elem_array(c, filter, elems))
   7451 				return 0;
   7452 			break;
   7453 		case NDB_FILTER_TAGS:
   7454 			if (!cursor_push_str(c, "\"#"))
   7455 				return 0;
   7456 			if (!cursor_push_byte(c, elems->field.tag))
   7457 				return 0;
   7458 			if (!cursor_push_str(c, "\":"))
   7459 				return 0;
   7460 			if (!cursor_push_json_elem_array(c, filter, elems))
   7461 				return 0;
   7462 			break;
   7463 		case NDB_FILTER_SINCE:
   7464 			if (!cursor_push_str(c, "\"since\":"))
   7465 				return 0;
   7466 			if (!cursor_push_int_str(c, ndb_filter_get_int_element(elems, 0)))
   7467 				return 0;
   7468 			break;
   7469 		case NDB_FILTER_UNTIL:
   7470 			if (!cursor_push_str(c, "\"until\":"))
   7471 				return 0;
   7472 			if (!cursor_push_int_str(c, ndb_filter_get_int_element(elems, 0)))
   7473 				return 0;
   7474 			break;
   7475 		case NDB_FILTER_LIMIT:
   7476 			if (!cursor_push_str(c, "\"limit\":"))
   7477 				return 0;
   7478 			if (!cursor_push_int_str(c, ndb_filter_get_int_element(elems, 0)))
   7479 				return 0;
   7480 			break;
   7481 		case NDB_FILTER_RELAYS:
   7482 			if (!cursor_push_str(c, "\"relays\":"))
   7483 				return 0;
   7484 			if (!cursor_push_json_elem_array(c, filter, elems))
   7485 				return 0;
   7486 			break;
   7487 		}
   7488 
   7489 		if (i != filter->num_elements-1) {
   7490 			if (!cursor_push_byte(c, ',')) {
   7491 				return 0;
   7492 			}
   7493 		}
   7494 
   7495 	}
   7496 
   7497 	if (!cursor_push_c_str(c, "}"))
   7498 		return 0;
   7499 
   7500 	return cur.p - cur.start;
   7501 }
   7502 
   7503 int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen, unsigned char *id) {
   7504 	int len;
   7505 
   7506 	if (!(len = ndb_event_commitment(note, buf, buflen)))
   7507 		return 0;
   7508 
   7509 	//fprintf(stderr, "%.*s\n", len, buf);
   7510 
   7511 	sha256((struct sha256*)id, buf, len);
   7512 
   7513 	return 1;
   7514 }
   7515 
   7516 int ndb_sign_id(struct ndb_keypair *keypair, unsigned char id[32],
   7517 		unsigned char sig[64])
   7518 {
   7519 	unsigned char aux[32];
   7520 	secp256k1_keypair *pair = (secp256k1_keypair*) keypair->pair;
   7521 
   7522 	if (!fill_random(aux, sizeof(aux)))
   7523 		return 0;
   7524 
   7525 	secp256k1_context *ctx =
   7526 		secp256k1_context_create(SECP256K1_CONTEXT_NONE);
   7527 
   7528 	return secp256k1_schnorrsig_sign32(ctx, sig, id, pair, aux);
   7529 }
   7530 
   7531 int ndb_create_keypair(struct ndb_keypair *kp)
   7532 {
   7533 	secp256k1_keypair *keypair = (secp256k1_keypair*)kp->pair;
   7534 	secp256k1_xonly_pubkey pubkey;
   7535 
   7536 	secp256k1_context *ctx =
   7537 		secp256k1_context_create(SECP256K1_CONTEXT_NONE);;
   7538 
   7539 	/* Try to create a keypair with a valid context, it should only
   7540 	 * fail if the secret key is zero or out of range. */
   7541 	if (!secp256k1_keypair_create(ctx, keypair, kp->secret))
   7542 		return 0;
   7543 
   7544 	if (!secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, keypair))
   7545 		return 0;
   7546 
   7547 	/* Serialize the public key. Should always return 1 for a valid public key. */
   7548 	return secp256k1_xonly_pubkey_serialize(ctx, kp->pubkey, &pubkey);
   7549 }
   7550 
   7551 int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair)
   7552 {
   7553 	if (!hex_decode(secstr, strlen(secstr), keypair->secret, 32)) {
   7554 		fprintf(stderr, "could not hex decode secret key\n");
   7555 		return 0;
   7556 	}
   7557 
   7558 	return ndb_create_keypair(keypair);
   7559 }
   7560 
   7561 int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note,
   7562 			 struct ndb_keypair *keypair)
   7563 {
   7564 	int strings_len = builder->strings.p - builder->strings.start;
   7565 	unsigned char *note_end = builder->note_cur.p + strings_len;
   7566 	int total_size = note_end - builder->note_cur.start;
   7567 
   7568 	// move the strings buffer next to the end of our ndb_note
   7569 	memmove(builder->note_cur.p, builder->strings.start, strings_len);
   7570 
   7571 	// set the strings location
   7572 	builder->note->strings = builder->note_cur.p - builder->note_cur.start;
   7573 
   7574 	// record the total size
   7575 	//builder->note->size = total_size;
   7576 
   7577 	*note = builder->note;
   7578 
   7579 	// generate id and sign if we're building this manually
   7580 	if (keypair) {
   7581 		// use the remaining memory for building our id buffer
   7582 		unsigned char *end   = builder->mem.end;
   7583 		unsigned char *start = (unsigned char*)(*note) + total_size;
   7584 
   7585 		ndb_builder_set_pubkey(builder, keypair->pubkey);
   7586 
   7587 		if (!ndb_calculate_id(builder->note, start, end - start,
   7588 				      builder->note->id))
   7589 			return 0;
   7590 
   7591 		if (!ndb_sign_id(keypair, (*note)->id, (*note)->sig))
   7592 			return 0;
   7593 	}
   7594 
   7595 	// make sure we're aligned as a whole
   7596 	total_size = (total_size + 7) & ~7;
   7597 	assert((total_size % 8) == 0);
   7598 	return total_size;
   7599 }
   7600 
   7601 struct ndb_note * ndb_builder_note(struct ndb_builder *builder)
   7602 {
   7603 	return builder->note;
   7604 }
   7605 
   7606 static union ndb_packed_str ndb_offset_str(uint32_t offset)
   7607 {
   7608 	// ensure accidents like -1 don't corrupt our packed_str
   7609 	union ndb_packed_str str;
   7610 	// most significant byte is reserved for ndb_packtype
   7611 	str.offset = offset & 0xFFFFFF;
   7612 	return str;
   7613 }
   7614 
   7615 
   7616 /// find an existing string via str_indices. these indices only exist in the
   7617 /// builder phase just for this purpose.
   7618 static inline int ndb_builder_find_str(struct ndb_builder *builder,
   7619 				       const char *str, int len,
   7620 				       union ndb_packed_str *pstr)
   7621 {
   7622 	// find existing matching string to avoid duplicate strings
   7623 	int indices = cursor_count(&builder->str_indices, sizeof(uint32_t));
   7624 	for (int i = 0; i < indices; i++) {
   7625 		uint32_t index = ((uint32_t*)builder->str_indices.start)[i];
   7626 		const char *some_str = (const char*)builder->strings.start + index;
   7627 
   7628 		if (!memcmp(some_str, str, len) && some_str[len] == '\0') {
   7629 			// found an existing matching str, use that index
   7630 			*pstr = ndb_offset_str(index);
   7631 			return 1;
   7632 		}
   7633 	}
   7634 
   7635 	return 0;
   7636 }
   7637 
   7638 static int ndb_builder_push_str(struct ndb_builder *builder, const char *str,
   7639 				int len, union ndb_packed_str *pstr)
   7640 {
   7641 	uint32_t loc;
   7642 
   7643 	// no string found, push a new one
   7644 	loc = builder->strings.p - builder->strings.start;
   7645 	if (!(cursor_push(&builder->strings, (unsigned char*)str, len) &&
   7646 	      cursor_push_byte(&builder->strings, '\0'))) {
   7647 		return 0;
   7648 	}
   7649 
   7650 	*pstr = ndb_offset_str(loc);
   7651 
   7652 	// record in builder indices. ignore return value, if we can't cache it
   7653 	// then whatever
   7654 	cursor_push_u32(&builder->str_indices, loc);
   7655 
   7656 	return 1;
   7657 }
   7658 
   7659 static int ndb_builder_push_packed_id(struct ndb_builder *builder,
   7660 				      unsigned char *id,
   7661 				      union ndb_packed_str *pstr)
   7662 {
   7663 	// Don't both find id duplicates. very rarely are they duplicated
   7664 	// and it slows things down quite a bit. If we really care about this
   7665 	// We can switch to a hash table.
   7666 	//if (ndb_builder_find_str(builder, (const char*)id, 32, pstr)) {
   7667 	//	pstr->packed.flag = NDB_PACKED_ID;
   7668 	//	return 1;
   7669 	//}
   7670 
   7671 	if (ndb_builder_push_str(builder, (const char*)id, 32, pstr)) {
   7672 		pstr->packed.flag = NDB_PACKED_ID;
   7673 		return 1;
   7674 	}
   7675 
   7676 	return 0;
   7677 }
   7678 
   7679 union ndb_packed_str ndb_chars_to_packed_str(char c1, char c2)
   7680 {
   7681 	union ndb_packed_str str;
   7682 	str.packed.flag = NDB_PACKED_STR;
   7683 	str.packed.str[0] = c1;
   7684 	str.packed.str[1] = c2;
   7685 	str.packed.str[2] = '\0';
   7686 	return str;
   7687 }
   7688 
   7689 static union ndb_packed_str ndb_char_to_packed_str(char c)
   7690 {
   7691 	union ndb_packed_str str;
   7692 	str.packed.flag = NDB_PACKED_STR;
   7693 	str.packed.str[0] = c;
   7694 	str.packed.str[1] = '\0';
   7695 	return str;
   7696 }
   7697 
   7698 
   7699 /// Check for small strings to pack
   7700 static inline int ndb_builder_try_compact_str(struct ndb_builder *builder,
   7701 					      const char *str, int len,
   7702 					      union ndb_packed_str *pstr,
   7703 					      int pack_ids)
   7704 {
   7705 	unsigned char id_buf[32];
   7706 
   7707 	if (len == 0) {
   7708 		*pstr = ndb_char_to_packed_str(0);
   7709 		return 1;
   7710 	} else if (len == 1) {
   7711 		*pstr = ndb_char_to_packed_str(str[0]);
   7712 		return 1;
   7713 	} else if (len == 2) {
   7714 		*pstr = ndb_chars_to_packed_str(str[0], str[1]);
   7715 		return 1;
   7716 	} else if (pack_ids && len == 64 && hex_decode(str, 64, id_buf, 32)) {
   7717 		return ndb_builder_push_packed_id(builder, id_buf, pstr);
   7718 	}
   7719 
   7720 	return 0;
   7721 }
   7722 
   7723 
   7724 static int ndb_builder_push_unpacked_str(struct ndb_builder *builder,
   7725 					 const char *str, int len,
   7726 					 union ndb_packed_str *pstr)
   7727 {
   7728 	if (ndb_builder_find_str(builder, str, len, pstr))
   7729 		return 1;
   7730 
   7731 	return ndb_builder_push_str(builder, str, len, pstr);
   7732 }
   7733 
   7734 int ndb_builder_make_str(struct ndb_builder *builder, const char *str, int len,
   7735 			 union ndb_packed_str *pstr, int pack_ids)
   7736 {
   7737 	if (ndb_builder_try_compact_str(builder, str, len, pstr, pack_ids))
   7738 		return 1;
   7739 
   7740 	return ndb_builder_push_unpacked_str(builder, str, len, pstr);
   7741 }
   7742 
   7743 int ndb_builder_set_content(struct ndb_builder *builder, const char *content,
   7744 			    int len)
   7745 {
   7746 	int pack_ids = 0;
   7747 	builder->note->content_length = len;
   7748 	return ndb_builder_make_str(builder, content, len,
   7749 				    &builder->note->content, pack_ids);
   7750 }
   7751 
   7752 
   7753 static inline int jsoneq(const char *json, jsmntok_t *tok, int tok_len,
   7754 			 const char *s)
   7755 {
   7756 	if (tok->type == JSMN_STRING && (int)strlen(s) == tok_len &&
   7757 	    memcmp(json + tok->start, s, tok_len) == 0) {
   7758 		return 1;
   7759 	}
   7760 	return 0;
   7761 }
   7762 
   7763 static int ndb_builder_finalize_tag(struct ndb_builder *builder,
   7764 				    union ndb_packed_str offset)
   7765 {
   7766 	if (!cursor_push_u32(&builder->note_cur, offset.offset))
   7767 		return 0;
   7768 	builder->current_tag->count++;
   7769 	return 1;
   7770 }
   7771 
   7772 /// Unescape and push json strings
   7773 static int ndb_builder_make_json_str(struct ndb_builder *builder,
   7774 				     const char *str, int len,
   7775 				     union ndb_packed_str *pstr,
   7776 				     int *written, int pack_ids)
   7777 {
   7778 	// let's not care about de-duping these. we should just unescape
   7779 	// in-place directly into the strings table.
   7780 	if (written)
   7781 		*written = len;
   7782 
   7783 	const char *p, *end, *start;
   7784 	unsigned char *builder_start;
   7785 
   7786 	// always try compact strings first
   7787 	if (ndb_builder_try_compact_str(builder, str, len, pstr, pack_ids))
   7788 		return 1;
   7789 
   7790 	end = str + len;
   7791 	start = str; // Initialize start to the beginning of the string
   7792 
   7793 	*pstr = ndb_offset_str(builder->strings.p - builder->strings.start);
   7794 	builder_start = builder->strings.p;
   7795 
   7796 	for (p = str; p < end; p++) {
   7797 		if (*p == '\\' && p+1 < end) {
   7798 			// Push the chunk of unescaped characters before this escape sequence
   7799 			if (start < p && !cursor_push(&builder->strings,
   7800 						(unsigned char *)start,
   7801 						p - start)) {
   7802 				return 0;
   7803 			}
   7804 
   7805 			if (!cursor_push_unescaped_char(&builder->strings, *p, *(p+1)))
   7806 				return 0;
   7807 
   7808 			p++; // Skip the character following the backslash
   7809 			start = p + 1; // Update the start pointer to the next character
   7810 		}
   7811 	}
   7812 
   7813 	// Handle the last chunk after the last escape sequence (or if there are no escape sequences at all)
   7814 	if (start < p && !cursor_push(&builder->strings, (unsigned char *)start,
   7815 				      p - start)) {
   7816 		return 0;
   7817 	}
   7818 
   7819 	if (written)
   7820 		*written = builder->strings.p - builder_start;
   7821 
   7822 	// TODO: dedupe these!?
   7823 	return cursor_push_byte(&builder->strings, '\0');
   7824 }
   7825 
   7826 static int ndb_builder_push_json_tag(struct ndb_builder *builder,
   7827 				     const char *str, int len)
   7828 {
   7829 	union ndb_packed_str pstr;
   7830 	int pack_ids = 1;
   7831 	if (!ndb_builder_make_json_str(builder, str, len, &pstr, NULL, pack_ids))
   7832 		return 0;
   7833 	return ndb_builder_finalize_tag(builder, pstr);
   7834 }
   7835 
   7836 // Push a json array into an ndb tag ["p", "abcd..."] -> struct ndb_tag
   7837 static int ndb_builder_tag_from_json_array(struct ndb_json_parser *p,
   7838 					   jsmntok_t *array)
   7839 {
   7840 	jsmntok_t *str_tok;
   7841 	const char *str;
   7842 
   7843 	if (array->size == 0)
   7844 		return 0;
   7845 
   7846 	if (!ndb_builder_new_tag(&p->builder))
   7847 		return 0;
   7848 
   7849 	for (int i = 0; i < array->size; i++) {
   7850 		str_tok = &array[i+1];
   7851 		str = p->json + str_tok->start;
   7852 
   7853 		if (!ndb_builder_push_json_tag(&p->builder, str,
   7854 					       toksize(str_tok))) {
   7855 			return 0;
   7856 		}
   7857 	}
   7858 
   7859 	return 1;
   7860 }
   7861 
   7862 // Push json tags into ndb data
   7863 //   [["t", "hashtag"], ["p", "abcde..."]] -> struct ndb_tags
   7864 static inline int ndb_builder_process_json_tags(struct ndb_json_parser *p,
   7865 						jsmntok_t *array)
   7866 {
   7867 	jsmntok_t *tag = array;
   7868 
   7869 	if (array->size == 0)
   7870 		return 1;
   7871 
   7872 	for (int i = 0; i < array->size; i++) {
   7873 		if (!ndb_builder_tag_from_json_array(p, &tag[i+1]))
   7874 			return 0;
   7875 		tag += tag[i+1].size;
   7876 	}
   7877 
   7878 	return 1;
   7879 }
   7880 
   7881 static int parse_unsigned_int(const char *start, int len, unsigned int *num)
   7882 {
   7883 	unsigned int number = 0;
   7884 	const char *p = start, *end = start + len;
   7885 	int digits = 0;
   7886 
   7887 	while (p < end) {
   7888 		char c = *p;
   7889 
   7890 		if (c < '0' || c > '9')
   7891 			break;
   7892 
   7893 		// Check for overflow
   7894 		char digit = c - '0';
   7895 		if (number > (UINT_MAX - digit) / 10)
   7896 			return 0; // Overflow detected
   7897 
   7898 		number = number * 10 + digit;
   7899 
   7900 		p++;
   7901 		digits++;
   7902 	}
   7903 
   7904 	if (digits == 0)
   7905 		return 0;
   7906 
   7907 	*num = number;
   7908 	return 1;
   7909 }
   7910 
   7911 int ndb_client_event_from_json(const char *json, int len, struct ndb_fce *fce,
   7912 			       unsigned char *buf, int bufsize, struct ndb_id_cb *cb)
   7913 {
   7914 	jsmntok_t *tok = NULL;
   7915 	int tok_len, res;
   7916 	struct ndb_json_parser parser;
   7917 	struct ndb_event *ev = &fce->event;
   7918 
   7919 	ndb_json_parser_init(&parser, json, len, buf, bufsize);
   7920 
   7921 	if ((res = ndb_json_parser_parse(&parser, cb)) < 0)
   7922 		return res;
   7923 
   7924 	if (parser.toks[0].type == JSMN_OBJECT) {
   7925 		ndb_debug("got raw json in client_event_from_json\n");
   7926 		fce->evtype = NDB_FCE_EVENT;
   7927 		return ndb_parse_json_note(&parser, &ev->note);
   7928 	}
   7929 
   7930 	if (parser.num_tokens <= 3 || parser.toks[0].type != JSMN_ARRAY)
   7931 		return 0;
   7932 
   7933 	parser.i = 1;
   7934 	tok = &parser.toks[parser.i++];
   7935 	tok_len = toksize(tok);
   7936 	if (tok->type != JSMN_STRING)
   7937 		return 0;
   7938 
   7939 	if (tok_len == 5 && !memcmp("EVENT", json + tok->start, 5)) {
   7940 		fce->evtype = NDB_FCE_EVENT;
   7941 		return ndb_parse_json_note(&parser, &ev->note);
   7942 	}
   7943 
   7944 	return 0;
   7945 }
   7946 
   7947 
   7948 int ndb_ws_event_from_json(const char *json, int len, struct ndb_tce *tce,
   7949 			   unsigned char *buf, int bufsize,
   7950 			   struct ndb_id_cb *cb)
   7951 {
   7952 	jsmntok_t *tok = NULL;
   7953 	int tok_len, res;
   7954 	struct ndb_json_parser parser;
   7955 	struct ndb_event *ev = &tce->event;
   7956 
   7957 	tce->subid_len = 0;
   7958 	tce->subid = "";
   7959 
   7960 	ndb_json_parser_init(&parser, json, len, buf, bufsize);
   7961 
   7962 	if ((res = ndb_json_parser_parse(&parser, cb)) < 0)
   7963 		return res;
   7964 
   7965 	if (parser.toks[0].type == JSMN_OBJECT) {
   7966 		ndb_debug("got raw json in ws_event_from_json\n");
   7967 		tce->evtype = NDB_TCE_EVENT;
   7968 		return ndb_parse_json_note(&parser, &ev->note);
   7969 	}
   7970 
   7971 	if (parser.num_tokens < 3 || parser.toks[0].type != JSMN_ARRAY)
   7972 		return 0;
   7973 
   7974 	parser.i = 1;
   7975 	tok = &parser.toks[parser.i++];
   7976 	tok_len = toksize(tok);
   7977 	if (tok->type != JSMN_STRING)
   7978 		return 0;
   7979 
   7980 	if (tok_len == 5 && !memcmp("EVENT", json + tok->start, 5)) {
   7981 		tce->evtype = NDB_TCE_EVENT;
   7982 
   7983 		tok = &parser.toks[parser.i++];
   7984 		if (tok->type != JSMN_STRING)
   7985 			return 0;
   7986 
   7987 		tce->subid = json + tok->start;
   7988 		tce->subid_len = toksize(tok);
   7989 
   7990 		return ndb_parse_json_note(&parser, &ev->note);
   7991 	} else if (tok_len == 4 && !memcmp("EOSE", json + tok->start, 4)) {
   7992 		tce->evtype = NDB_TCE_EOSE;
   7993 
   7994 		tok = &parser.toks[parser.i++];
   7995 		if (tok->type != JSMN_STRING)
   7996 			return 0;
   7997 
   7998 		tce->subid = json + tok->start;
   7999 		tce->subid_len = toksize(tok);
   8000 		return 1;
   8001 	} else if (tok_len == 2 && !memcmp("OK", json + tok->start, 2)) {
   8002 		if (parser.num_tokens != 5)
   8003 			return 0;
   8004 
   8005 		struct ndb_command_result *cr = &tce->command_result;
   8006 
   8007 		tce->evtype = NDB_TCE_OK;
   8008 
   8009 		tok = &parser.toks[parser.i++];
   8010 		if (tok->type != JSMN_STRING)
   8011 			return 0;
   8012 
   8013 		tce->subid = json + tok->start;
   8014 		tce->subid_len = toksize(tok);
   8015 
   8016 		tok = &parser.toks[parser.i++];
   8017 		if (tok->type != JSMN_PRIMITIVE || toksize(tok) == 0)
   8018 			return 0;
   8019 
   8020 		cr->ok = (json + tok->start)[0] == 't';
   8021 
   8022 		tok = &parser.toks[parser.i++];
   8023 		if (tok->type != JSMN_STRING)
   8024 			return 0;
   8025 
   8026 		tce->command_result.msg = json + tok->start;
   8027 		tce->command_result.msglen = toksize(tok);
   8028 
   8029 		return 1;
   8030 	} else if (tok_len == 4 && !memcmp("AUTH", json + tok->start, 4)) {
   8031 		tce->evtype = NDB_TCE_AUTH;
   8032 
   8033 		tok = &parser.toks[parser.i++];
   8034 		if (tok->type != JSMN_STRING)
   8035 			return 0;
   8036 
   8037 		tce->subid = json + tok->start;
   8038 		tce->subid_len = toksize(tok);
   8039 
   8040 		return 1;
   8041 	}
   8042 
   8043 	return 0;
   8044 }
   8045 
   8046 static enum ndb_filter_fieldtype
   8047 ndb_filter_parse_field(const char *tok, int len, char *tagchar)
   8048 {
   8049 	*tagchar = 0;
   8050 
   8051 	if (len == 0)
   8052 		return 0;
   8053 
   8054 	if (len == 7 && !strncmp(tok, "authors", 7)) {
   8055 		return NDB_FILTER_AUTHORS;
   8056 	} else if (len == 3 && !strncmp(tok, "ids", 3)) {
   8057 		return NDB_FILTER_IDS;
   8058 	} else if (len == 5 && !strncmp(tok, "kinds", 5)) {
   8059 		return NDB_FILTER_KINDS;
   8060 	} else if (len == 2 && tok[0] == '#') {
   8061 		*tagchar = tok[1];
   8062 		return NDB_FILTER_TAGS;
   8063 	} else if (len == 5 && !strncmp(tok, "since", 5)) {
   8064 		return NDB_FILTER_SINCE;
   8065 	} else if (len == 5 && !strncmp(tok, "until", 5)) {
   8066 		return NDB_FILTER_UNTIL;
   8067 	} else if (len == 5 && !strncmp(tok, "limit", 5)) {
   8068 		return NDB_FILTER_LIMIT;
   8069 	} else if (len == 6 && !strncmp(tok, "search", 6)) {
   8070 		return NDB_FILTER_SEARCH;
   8071 	}
   8072 
   8073 	return 0;
   8074 }
   8075 
   8076 static int ndb_filter_parse_json_ids(struct ndb_json_parser *parser,
   8077 				     struct ndb_filter *filter)
   8078 {
   8079 	jsmntok_t *tok;
   8080 	const char *start;
   8081 	unsigned char hexbuf[32];
   8082 	int tok_len, i, size;
   8083 
   8084 	tok = &parser->toks[parser->i++];
   8085 
   8086 	if (tok->type != JSMN_ARRAY) {
   8087 		ndb_debug("parse_json_ids: not an array\n");
   8088 		return 0;
   8089 	}
   8090 
   8091 	size = tok->size;
   8092 
   8093 	for (i = 0; i < size; parser->i++, i++) {
   8094 		tok = &parser->toks[parser->i];
   8095 		start = parser->json + tok->start;
   8096 		tok_len = toksize(tok);
   8097 
   8098 		if (tok->type != JSMN_STRING) {
   8099 			ndb_debug("parse_json_ids: not a string '%d'\n", tok->type);
   8100 			return 0;
   8101 		}
   8102 
   8103 		if (tok_len != 64) {
   8104 			ndb_debug("parse_json_ids: not len 64: '%.*s'\n", tok_len, start);
   8105 			return 0;
   8106 		}
   8107 
   8108 		// id
   8109 		if (!hex_decode(start, tok_len, hexbuf, sizeof(hexbuf))) {
   8110 			ndb_debug("parse_json_ids: hex decode failed\n");
   8111 			return 0;
   8112 		}
   8113 
   8114 		ndb_debug("adding id elem\n");
   8115 		if (!ndb_filter_add_id_element(filter, hexbuf)) {
   8116 			ndb_debug("parse_json_ids: failed to add id element\n");
   8117 			return 0;
   8118 		}
   8119 	}
   8120 
   8121 	parser->i--;
   8122 	return 1;
   8123 }
   8124 
   8125 static int ndb_filter_parse_json_elems(struct ndb_json_parser *parser,
   8126 				       struct ndb_filter *filter)
   8127 {
   8128 	jsmntok_t *tok;
   8129 	const char *start;
   8130 	int tok_len;
   8131 	unsigned char hexbuf[32];
   8132 	enum ndb_generic_element_type typ;
   8133 	tok = NULL;
   8134 	int i, size;
   8135 
   8136 	tok = &parser->toks[parser->i++];
   8137 
   8138 	if (tok->type != JSMN_ARRAY)
   8139 		return 0;
   8140 
   8141 	size = tok->size;
   8142 
   8143 	for (i = 0; i < size; i++, parser->i++) {
   8144 		tok = &parser->toks[parser->i];
   8145 		start = parser->json + tok->start;
   8146 		tok_len = toksize(tok);
   8147 
   8148 		if (tok->type != JSMN_STRING)
   8149 			return 0;
   8150 
   8151 		if (i == 0) {
   8152 			if (tok_len == 64 && hex_decode(start, 64, hexbuf, sizeof(hexbuf))) {
   8153 				typ = NDB_ELEMENT_ID;
   8154 				if (!ndb_filter_add_id_element(filter, hexbuf)) {
   8155 					ndb_debug("failed to push id elem\n");
   8156 					return 0;
   8157 				}
   8158 			} else {
   8159 				typ = NDB_ELEMENT_STRING;
   8160 				if (!ndb_filter_add_str_element_len(filter, start, tok_len))
   8161 					return 0;
   8162 			}
   8163 		} else if (typ == NDB_ELEMENT_ID) {
   8164 			if (!hex_decode(start, 64, hexbuf, sizeof(hexbuf)))
   8165 				return 0;
   8166 			if (!ndb_filter_add_id_element(filter, hexbuf))
   8167 				return 0;
   8168 		} else if (typ == NDB_ELEMENT_STRING) {
   8169 			if (!ndb_filter_add_str_element_len(filter, start, tok_len))
   8170 				return 0;
   8171 		} else {
   8172 			// ???
   8173 			return 0;
   8174 		}
   8175 	}
   8176 
   8177 	parser->i--;
   8178 	return 1;
   8179 }
   8180 
   8181 static int ndb_filter_parse_json_str(struct ndb_json_parser *parser,
   8182 				     struct ndb_filter *filter)
   8183 {
   8184 	jsmntok_t *tok;
   8185 	const char *start;
   8186 	int tok_len;
   8187 
   8188 	tok = &parser->toks[parser->i];
   8189 	start = parser->json + tok->start;
   8190 	tok_len = toksize(tok);
   8191 
   8192 	if (tok->type != JSMN_STRING)
   8193 		return 0;
   8194 
   8195 	if (!ndb_filter_add_str_element_len(filter, start, tok_len))
   8196 		return 0;
   8197 
   8198 	ndb_debug("added str elem '%.*s'\n", tok_len, start);
   8199 
   8200 	return 1;
   8201 }
   8202 
   8203 static int ndb_filter_parse_json_int(struct ndb_json_parser *parser,
   8204 				     struct ndb_filter *filter)
   8205 {
   8206 	jsmntok_t *tok;
   8207 	const char *start;
   8208 	int tok_len;
   8209 	unsigned int value;
   8210 
   8211 	tok = &parser->toks[parser->i];
   8212 	start = parser->json + tok->start;
   8213 	tok_len = toksize(tok);
   8214 
   8215 	if (tok->type != JSMN_PRIMITIVE)
   8216 		return 0;
   8217 
   8218 	if (!parse_unsigned_int(start, tok_len, &value))
   8219 		return 0;
   8220 
   8221 	if (!ndb_filter_add_int_element(filter, (uint64_t)value))
   8222 		return 0;
   8223 
   8224 	ndb_debug("added int elem %d\n", value);
   8225 
   8226 	return 1;
   8227 }
   8228 
   8229 
   8230 static int ndb_filter_parse_json_ints(struct ndb_json_parser *parser,
   8231 				      struct ndb_filter *filter)
   8232 {
   8233 	jsmntok_t *tok;
   8234 	int size, i;
   8235 
   8236 	tok = &parser->toks[parser->i++];
   8237 
   8238 	if (tok->type != JSMN_ARRAY)
   8239 		return 0;
   8240 
   8241 	size = tok->size;
   8242 
   8243 	for (i = 0; i < size; parser->i++, i++) {
   8244 		if (!ndb_filter_parse_json_int(parser, filter))
   8245 			return 0;
   8246 	}
   8247 
   8248 	parser->i--;
   8249 	return 1;
   8250 }
   8251 
   8252 
   8253 static int ndb_filter_parse_json(struct ndb_json_parser *parser,
   8254 				 struct ndb_filter *filter)
   8255 {
   8256 	jsmntok_t *tok = NULL;
   8257 	const char *json = parser->json;
   8258 	const char *start;
   8259 	char tag;
   8260 	int tok_len;
   8261 	enum ndb_filter_fieldtype field;
   8262 
   8263 	if (parser->toks[parser->i++].type != JSMN_OBJECT)
   8264 		return 0;
   8265 
   8266 	for (; parser->i < parser->num_tokens; parser->i++) {
   8267 		tok = &parser->toks[parser->i++];
   8268 		start = json + tok->start;
   8269 		tok_len = toksize(tok);
   8270 
   8271 		if (!(field = ndb_filter_parse_field(start, tok_len, &tag))) {
   8272 			ndb_debug("failed field '%.*s'\n", tok_len, start);
   8273 			continue;
   8274 		}
   8275 
   8276 		if (tag) {
   8277 			ndb_debug("starting tag field '%c'\n", tag);
   8278 			if (!ndb_filter_start_tag_field(filter, tag)) {
   8279 				ndb_debug("failed to start tag field '%c'\n", tag);
   8280 				return 0;
   8281 			}
   8282 		} else if (!ndb_filter_start_field(filter, field)) {
   8283 			ndb_debug("field already started\n");
   8284 			return 0;
   8285 		}
   8286 
   8287 		// we parsed a top-level field
   8288 		switch(field) {
   8289 		case NDB_FILTER_CUSTOM:
   8290 			// can't really parse these yet
   8291 			break;
   8292 		case NDB_FILTER_AUTHORS:
   8293 		case NDB_FILTER_IDS:
   8294 			if (!ndb_filter_parse_json_ids(parser, filter)) {
   8295 				ndb_debug("failed to parse filter ids/authors\n");
   8296 				return 0;
   8297 			}
   8298 			break;
   8299 		case NDB_FILTER_SEARCH:
   8300 			if (!ndb_filter_parse_json_str(parser, filter)) {
   8301 				ndb_debug("failed to parse filter search str\n");
   8302 				return 0;
   8303 			}
   8304 			break;
   8305 		case NDB_FILTER_SINCE:
   8306 		case NDB_FILTER_UNTIL:
   8307 		case NDB_FILTER_LIMIT:
   8308 			if (!ndb_filter_parse_json_int(parser, filter)) {
   8309 				ndb_debug("failed to parse filter since/until/limit\n");
   8310 				return 0;
   8311 			}
   8312 			break;
   8313 		case NDB_FILTER_KINDS:
   8314 			if (!ndb_filter_parse_json_ints(parser, filter)) {
   8315 				ndb_debug("failed to parse filter kinds\n");
   8316 				return 0;
   8317 			}
   8318 			break;
   8319 		case NDB_FILTER_RELAYS:
   8320 		case NDB_FILTER_TAGS:
   8321 			if (!ndb_filter_parse_json_elems(parser, filter)) {
   8322 				ndb_debug("failed to parse filter tags\n");
   8323 				return 0;
   8324 			}
   8325 			break;
   8326 		}
   8327 
   8328 		ndb_filter_end_field(filter);
   8329 	}
   8330 
   8331 	return ndb_filter_end(filter);
   8332 }
   8333 
   8334 int ndb_parse_json_note(struct ndb_json_parser *parser, struct ndb_note **note)
   8335 {
   8336 	jsmntok_t *tok = NULL;
   8337 	unsigned char hexbuf[64];
   8338 	const char *json = parser->json;
   8339 	const char *start;
   8340 	int i, tok_len, parsed;
   8341 
   8342 	parsed = 0;
   8343 
   8344 	if (parser->toks[parser->i].type != JSMN_OBJECT)
   8345 		return 0;
   8346 
   8347 	// TODO: build id buffer and verify at end
   8348 
   8349 	for (i = parser->i + 1; i < parser->num_tokens; i++) {
   8350 		tok = &parser->toks[i];
   8351 		start = json + tok->start;
   8352 		tok_len = toksize(tok);
   8353 
   8354 		//printf("toplevel %.*s %d\n", tok_len, json + tok->start, tok->type);
   8355 		if (tok_len == 0 || i + 1 >= parser->num_tokens)
   8356 			continue;
   8357 
   8358 		if (start[0] == 'p' && jsoneq(json, tok, tok_len, "pubkey")) {
   8359 			// pubkey
   8360 			tok = &parser->toks[i+1];
   8361 			hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
   8362 			parsed |= NDB_PARSED_PUBKEY;
   8363 			ndb_builder_set_pubkey(&parser->builder, hexbuf);
   8364 		} else if (tok_len == 2 && start[0] == 'i' && start[1] == 'd') {
   8365 			// id
   8366 			tok = &parser->toks[i+1];
   8367 			hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
   8368 			parsed |= NDB_PARSED_ID;
   8369 			ndb_builder_set_id(&parser->builder, hexbuf);
   8370 		} else if (tok_len == 3 && start[0] == 's' && start[1] == 'i' && start[2] == 'g') {
   8371 			// sig
   8372 			tok = &parser->toks[i+1];
   8373 			hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
   8374 			parsed |= NDB_PARSED_SIG;
   8375 			ndb_builder_set_sig(&parser->builder, hexbuf);
   8376 		} else if (start[0] == 'k' && jsoneq(json, tok, tok_len, "kind")) {
   8377 			// kind
   8378 			tok = &parser->toks[i+1];
   8379 			start = json + tok->start;
   8380 			if (tok->type != JSMN_PRIMITIVE || tok_len <= 0)
   8381 				return 0;
   8382 			if (!parse_unsigned_int(start, toksize(tok),
   8383 						&parser->builder.note->kind))
   8384 					return 0;
   8385 			parsed |= NDB_PARSED_KIND;
   8386 		} else if (start[0] == 'c') {
   8387 			if (jsoneq(json, tok, tok_len, "created_at")) {
   8388 				// created_at
   8389 				tok = &parser->toks[i+1];
   8390 				start = json + tok->start;
   8391 				if (tok->type != JSMN_PRIMITIVE || tok_len <= 0)
   8392 					return 0;
   8393 				// TODO: update to int64 in 2106 ... xD
   8394 				unsigned int bigi;
   8395 				if (!parse_unsigned_int(start, toksize(tok), &bigi))
   8396 					return 0;
   8397 				parser->builder.note->created_at = bigi;
   8398 				parsed |= NDB_PARSED_CREATED_AT;
   8399 			} else if (jsoneq(json, tok, tok_len, "content")) {
   8400 				// content
   8401 				tok = &parser->toks[i+1];
   8402 				union ndb_packed_str pstr;
   8403 				tok_len = toksize(tok);
   8404 				int written, pack_ids = 0;
   8405 				if (!ndb_builder_make_json_str(&parser->builder,
   8406 							json + tok->start,
   8407 							tok_len, &pstr,
   8408 							&written, pack_ids)) {
   8409 					ndb_debug("ndb_builder_make_json_str failed\n");
   8410 					return 0;
   8411 				}
   8412 				parser->builder.note->content_length = written;
   8413 				parser->builder.note->content = pstr;
   8414 				parsed |= NDB_PARSED_CONTENT;
   8415 			}
   8416 		} else if (start[0] == 't' && jsoneq(json, tok, tok_len, "tags")) {
   8417 			tok = &parser->toks[i+1];
   8418 			ndb_builder_process_json_tags(parser, tok);
   8419 			i += tok->size;
   8420 			parsed |= NDB_PARSED_TAGS;
   8421 		}
   8422 	}
   8423 
   8424 	//ndb_debug("parsed %d = %d, &->%d", parsed, NDB_PARSED_ALL, parsed & NDB_PARSED_ALL);
   8425 	if (parsed != NDB_PARSED_ALL)
   8426 		return 0;
   8427 
   8428 	return ndb_builder_finalize(&parser->builder, note, NULL);
   8429 }
   8430 
   8431 int ndb_filter_from_json(const char *json, int len, struct ndb_filter *filter,
   8432 			 unsigned char *buf, int bufsize)
   8433 {
   8434 	struct ndb_json_parser parser;
   8435 	int res;
   8436 
   8437 	if (filter->finalized)
   8438 		return 0;
   8439 
   8440 	ndb_json_parser_init(&parser, json, len, buf, bufsize);
   8441 	if ((res = ndb_json_parser_parse(&parser, NULL)) < 0)
   8442 		return res;
   8443 
   8444 	if (parser.num_tokens < 1)
   8445 		return 0;
   8446 
   8447 	return ndb_filter_parse_json(&parser, filter);
   8448 }
   8449 
   8450 int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
   8451 		       unsigned char *buf, int bufsize)
   8452 {
   8453 	struct ndb_json_parser parser;
   8454 	int res;
   8455 
   8456 	ndb_json_parser_init(&parser, json, len, buf, bufsize);
   8457 	if ((res = ndb_json_parser_parse(&parser, NULL)) < 0)
   8458 		return res;
   8459 
   8460 	if (parser.num_tokens < 1)
   8461 		return 0;
   8462 
   8463 	return ndb_parse_json_note(&parser, note);
   8464 }
   8465 
   8466 void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey)
   8467 {
   8468 	memcpy(builder->note->pubkey, pubkey, 32);
   8469 }
   8470 
   8471 void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id)
   8472 {
   8473 	memcpy(builder->note->id, id, 32);
   8474 }
   8475 
   8476 void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig)
   8477 {
   8478 	memcpy(builder->note->sig, sig, 64);
   8479 }
   8480 
   8481 void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind)
   8482 {
   8483 	builder->note->kind = kind;
   8484 }
   8485 
   8486 void ndb_builder_set_created_at(struct ndb_builder *builder, uint64_t created_at)
   8487 {
   8488 	builder->note->created_at = created_at;
   8489 }
   8490 
   8491 int ndb_builder_new_tag(struct ndb_builder *builder)
   8492 {
   8493 	builder->note->tags.count++;
   8494 	struct ndb_tag tag = {0};
   8495 	builder->current_tag = (struct ndb_tag *)builder->note_cur.p;
   8496 	return cursor_push_tag(&builder->note_cur, &tag);
   8497 }
   8498 
   8499 void ndb_stat_counts_init(struct ndb_stat_counts *counts)
   8500 {
   8501 	counts->count = 0;
   8502 	counts->key_size = 0;
   8503 	counts->value_size = 0;
   8504 }
   8505 
   8506 static void ndb_stat_init(struct ndb_stat *stat)
   8507 {
   8508 	// init stats
   8509 	int i;
   8510 
   8511 	for (i = 0; i < NDB_CKIND_COUNT; i++) {
   8512 		ndb_stat_counts_init(&stat->common_kinds[i]);
   8513 	}
   8514 
   8515 	for (i = 0; i < NDB_DBS; i++) {
   8516 		ndb_stat_counts_init(&stat->dbs[i]);
   8517 	}
   8518 
   8519 	ndb_stat_counts_init(&stat->other_kinds);
   8520 }
   8521 
   8522 int ndb_stat(struct ndb *ndb, struct ndb_stat *stat)
   8523 {
   8524 	int rc;
   8525 	MDB_cursor *cur;
   8526 	MDB_val k, v;
   8527 	MDB_dbi db;
   8528 	struct ndb_txn txn;
   8529 	struct ndb_note *note;
   8530 	int i;
   8531 	enum ndb_common_kind common_kind;
   8532 
   8533 	// initialize to 0
   8534 	ndb_stat_init(stat);
   8535 
   8536 	if (!ndb_begin_query(ndb, &txn)) {
   8537 		fprintf(stderr, "ndb_stat failed at ndb_begin_query\n");
   8538 		return 0;
   8539 	}
   8540 
   8541 	// stat each dbi in the database
   8542 	for (i = 0; i < NDB_DBS; i++)
   8543 	{
   8544 		db = ndb->lmdb.dbs[i];
   8545 
   8546 		if ((rc = mdb_cursor_open(txn.mdb_txn, db, &cur))) {
   8547 			fprintf(stderr, "ndb_stat: mdb_cursor_open failed, error '%s'\n",
   8548 					mdb_strerror(rc));
   8549 			return 0;
   8550 		}
   8551 
   8552 		// loop over every entry and count kv sizes
   8553 		while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8554 			// we gather more detailed per-kind stats if we're in
   8555 			// the notes db
   8556 			if (i == NDB_DB_NOTE) {
   8557 				note = v.mv_data;
   8558 				common_kind = ndb_kind_to_common_kind(note->kind);
   8559 
   8560 				// uncommon kind? just count them in bulk
   8561 				if ((int)common_kind == -1) {
   8562 					stat->other_kinds.count++;
   8563 					stat->other_kinds.key_size += k.mv_size;
   8564 					stat->other_kinds.value_size += v.mv_size;
   8565 				} else {
   8566 					stat->common_kinds[common_kind].count++;
   8567 					stat->common_kinds[common_kind].key_size += k.mv_size;
   8568 					stat->common_kinds[common_kind].value_size += v.mv_size;
   8569 				}
   8570 			}
   8571 
   8572 			stat->dbs[i].count++;
   8573 			stat->dbs[i].key_size += k.mv_size;
   8574 			stat->dbs[i].value_size += v.mv_size;
   8575 		}
   8576 
   8577 		// close the cursor, they are per-dbi
   8578 		mdb_cursor_close(cur);
   8579 	}
   8580 
   8581 	ndb_end_query(&txn);
   8582 
   8583 	return 1;
   8584 }
   8585 
   8586 /// Push an element to the current tag
   8587 ///
   8588 /// Basic idea is to call ndb_builder_new_tag
   8589 int ndb_builder_push_tag_str(struct ndb_builder *builder,
   8590 				    const char *str, int len)
   8591 {
   8592 	union ndb_packed_str pstr;
   8593 	int pack_ids = 1;
   8594 	if (!ndb_builder_make_str(builder, str, len, &pstr, pack_ids))
   8595 		return 0;
   8596 	return ndb_builder_finalize_tag(builder, pstr);
   8597 }
   8598 
   8599 /// Push an id element to the current tag. Needs to be 32 bytes
   8600 int ndb_builder_push_tag_id(struct ndb_builder *builder,
   8601 			    unsigned char *id)
   8602 {
   8603 	union ndb_packed_str pstr;
   8604 	if (!ndb_builder_push_packed_id(builder, id, &pstr))
   8605 		return 0;
   8606 	return ndb_builder_finalize_tag(builder, pstr);
   8607 }
   8608 
   8609 //
   8610 // CONFIG
   8611 //
   8612 void ndb_default_config(struct ndb_config *config)
   8613 {
   8614 	int cores = get_cpu_cores();
   8615 	config->mapsize = 1024UL * 1024UL * 1024UL * 32UL; // 32 GiB
   8616 	config->ingester_threads = cores == -1 ? 4 : cores;
   8617 	config->flags = 0;
   8618 	config->ingest_filter = NULL;
   8619 	config->filter_context = NULL;
   8620 	config->sub_cb_ctx = NULL;
   8621 	config->sub_cb = NULL;
   8622 	config->writer_scratch_buffer_size = DEFAULT_WRITER_SCRATCH_SIZE;
   8623 }
   8624 
   8625 void ndb_config_set_subscription_callback(struct ndb_config *config, ndb_sub_fn fn, void *context)
   8626 {
   8627 	config->sub_cb_ctx = context;
   8628 	config->sub_cb = fn;
   8629 }
   8630 
   8631 void ndb_config_set_writer_scratch_buffer_size(struct ndb_config *config, int scratch_size)
   8632 {
   8633 	config->writer_scratch_buffer_size = scratch_size;
   8634 }
   8635 
   8636 void ndb_config_set_ingest_threads(struct ndb_config *config, int threads)
   8637 {
   8638 	config->ingester_threads = threads;
   8639 }
   8640 
   8641 void ndb_config_set_flags(struct ndb_config *config, int flags)
   8642 {
   8643 	config->flags = flags;
   8644 }
   8645 
   8646 void ndb_config_set_mapsize(struct ndb_config *config, size_t mapsize)
   8647 {
   8648 	config->mapsize = mapsize;
   8649 }
   8650 
   8651 void ndb_config_set_ingest_filter(struct ndb_config *config,
   8652 				  ndb_ingest_filter_fn fn, void *filter_ctx)
   8653 {
   8654 	config->ingest_filter = fn;
   8655 	config->filter_context = filter_ctx;
   8656 }
   8657 
   8658 int ndb_print_note_metadata(struct ndb_txn *txn)
   8659 {
   8660 	MDB_cursor *cur;
   8661 	MDB_val k, v;
   8662 	int i;
   8663 
   8664 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &cur))
   8665 		return 0;
   8666 
   8667 	i = 1;
   8668 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8669 		print_hex(k.mv_data, 32);
   8670 		printf("\t");
   8671 		print_note_meta((struct ndb_note_meta*)v.mv_data);
   8672 		i++;
   8673 	}
   8674 
   8675 	mdb_cursor_close(cur);
   8676 
   8677 	return i;
   8678 }
   8679 
   8680 
   8681 int ndb_print_author_kind_index(struct ndb_txn *txn)
   8682 {
   8683 	MDB_cursor *cur;
   8684 	struct ndb_id_u64_ts *key;
   8685 	MDB_val k, v;
   8686 	int i;
   8687 
   8688 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND], &cur))
   8689 		return 0;
   8690 
   8691 	i = 1;
   8692 	printf("author\tkind\tcreated_at\tnote_id\n");
   8693 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8694 		key = (struct ndb_id_u64_ts *)k.mv_data;
   8695 		print_hex(key->id, 32);
   8696 		printf("\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\n",
   8697 				key->u64, key->timestamp, *(uint64_t*)v.mv_data);
   8698 		i++;
   8699 	}
   8700 
   8701 	mdb_cursor_close(cur);
   8702 
   8703 	return i;
   8704 }
   8705 
   8706 int ndb_print_relay_kind_index(struct ndb_txn *txn)
   8707 {
   8708 	MDB_cursor *cur;
   8709 	unsigned char *d;
   8710 	MDB_val k, v;
   8711 	int i;
   8712 
   8713 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAY_KIND], &cur))
   8714 		return 0;
   8715 
   8716 	i = 1;
   8717 	printf("relay\tkind\tcreated_at\tnote_id\n");
   8718 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8719 		d = (unsigned char *)k.mv_data;
   8720 		printf("%s\t", (const char *)(d + 25));
   8721 		printf("%" PRIu64 "\t", *(uint64_t*)(d + 8));
   8722 		printf("%" PRIu64 "\t", *(uint64_t*)(d + 16));
   8723 		printf("%" PRIu64 "\n", *(uint64_t*)(d + 0));
   8724 		i++;
   8725 	}
   8726 
   8727 	mdb_cursor_close(cur);
   8728 
   8729 	return i;
   8730 }
   8731 
   8732 int ndb_print_tag_index(struct ndb_txn *txn)
   8733 {
   8734 	MDB_cursor *cur;
   8735 	MDB_val k, v;
   8736 	int i;
   8737 
   8738 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_TAGS], &cur))
   8739 		return 0;
   8740 
   8741 	i = 1;
   8742 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8743 		printf("%d ", i);
   8744 		print_tag_kv(txn, &k, &v);
   8745 		i++;
   8746 	}
   8747 
   8748 	mdb_cursor_close(cur);
   8749 
   8750 	return 1;
   8751 }
   8752 
   8753 int ndb_print_kind_keys(struct ndb_txn *txn)
   8754 {
   8755 	MDB_cursor *cur;
   8756 	MDB_val k, v;
   8757 	int i;
   8758 	struct ndb_u64_ts *tsid;
   8759 
   8760 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_KIND], &cur))
   8761 		return 0;
   8762 
   8763 	i = 1;
   8764 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8765 		tsid = k.mv_data;
   8766 		printf("%d note_kind %" PRIu64 " %" PRIu64 "\n",
   8767 			i, tsid->u64, tsid->timestamp);
   8768 
   8769 		i++;
   8770 	}
   8771 
   8772 	mdb_cursor_close(cur);
   8773 
   8774 	return 1;
   8775 }
   8776 
   8777 // used by ndb.c
   8778 int ndb_print_search_keys(struct ndb_txn *txn)
   8779 {
   8780 	MDB_cursor *cur;
   8781 	MDB_val k, v;
   8782 	int i;
   8783 	struct ndb_text_search_key search_key;
   8784 
   8785 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_TEXT], &cur))
   8786 		return 0;
   8787 
   8788 	i = 1;
   8789 	while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) {
   8790 		if (!ndb_unpack_text_search_key(k.mv_data, k.mv_size, &search_key)) {
   8791 			fprintf(stderr, "error decoding key %d\n", i);
   8792 			continue;
   8793 		}
   8794 
   8795 		ndb_print_text_search_key(k.mv_size, &search_key);
   8796 		printf("\n");
   8797 
   8798 		i++;
   8799 	}
   8800 
   8801 	mdb_cursor_close(cur);
   8802 
   8803 	return 1;
   8804 }
   8805 
   8806 struct ndb_tags *ndb_note_tags(struct ndb_note *note)
   8807 {
   8808 	return &note->tags;
   8809 }
   8810 
   8811 struct ndb_str ndb_note_str(struct ndb_note *note, union ndb_packed_str *pstr)
   8812 {
   8813 	struct ndb_str str;
   8814 	str.flag = pstr->packed.flag;
   8815 
   8816 	if (str.flag == NDB_PACKED_STR) {
   8817 		str.str = pstr->packed.str;
   8818 		return str;
   8819 	}
   8820 
   8821 	str.str = ((const char *)note) + note->strings + (pstr->offset & 0xFFFFFF);
   8822 	return str;
   8823 }
   8824 
   8825 struct ndb_str ndb_tag_str(struct ndb_note *note, struct ndb_tag *tag, int ind)
   8826 {
   8827 	return ndb_note_str(note, &tag->strs[ind]);
   8828 }
   8829 
   8830 int ndb_str_len(struct ndb_str *str)
   8831 {
   8832 	if (str->flag == NDB_PACKED_ID)
   8833 		return 32;
   8834 	return strlen(str->str);
   8835 }
   8836 
   8837 struct ndb_str ndb_iter_tag_str(struct ndb_iterator *iter, int ind)
   8838 {
   8839 	return ndb_tag_str(iter->note, iter->tag, ind);
   8840 }
   8841 
   8842 unsigned char * ndb_note_id(struct ndb_note *note)
   8843 {
   8844 	return note->id;
   8845 }
   8846 
   8847 unsigned char * ndb_note_pubkey(struct ndb_note *note)
   8848 {
   8849 	return note->pubkey;
   8850 }
   8851 
   8852 unsigned char * ndb_note_sig(struct ndb_note *note)
   8853 {
   8854 	return note->sig;
   8855 }
   8856 
   8857 uint32_t ndb_note_created_at(struct ndb_note *note)
   8858 {
   8859 	return note->created_at;
   8860 }
   8861 
   8862 uint32_t ndb_note_kind(struct ndb_note *note)
   8863 {
   8864 	return note->kind;
   8865 }
   8866 
   8867 void _ndb_note_set_kind(struct ndb_note *note, uint32_t kind)
   8868 {
   8869 	note->kind = kind;
   8870 }
   8871 
   8872 const char *ndb_note_content(struct ndb_note *note)
   8873 {
   8874 	return ndb_note_str(note, &note->content).str;
   8875 }
   8876 
   8877 uint32_t ndb_note_content_length(struct ndb_note *note)
   8878 {
   8879 	return note->content_length;
   8880 }
   8881 
   8882 struct ndb_note * ndb_note_from_bytes(unsigned char *bytes)
   8883 {
   8884 	struct ndb_note *note = (struct ndb_note *)bytes;
   8885 	if (note->version != 1)
   8886 		return 0;
   8887 	return note;
   8888 }
   8889 
   8890 int ndb_note_relay_iterate_start(struct ndb_txn *txn,
   8891 				 struct ndb_note_relay_iterator *iter,
   8892 				 uint64_t note_key)
   8893 {
   8894 	if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAYS],
   8895 			    (MDB_cursor**)&iter->mdb_cur)) {
   8896 		return 0;
   8897 	}
   8898 
   8899 	iter->txn = txn;
   8900 	iter->cursor_op = MDB_SET_KEY;
   8901 	iter->note_key = note_key;
   8902 
   8903 	return 1;
   8904 }
   8905 
   8906 const char *ndb_note_relay_iterate_next(struct ndb_note_relay_iterator *iter)
   8907 {
   8908 	int rc;
   8909 	MDB_val k, v;
   8910 
   8911 	if (iter->mdb_cur == NULL)
   8912 		return NULL;
   8913 
   8914 	k.mv_data = &iter->note_key;
   8915 	k.mv_size = sizeof(iter->note_key);
   8916 
   8917 	if ((rc = mdb_cursor_get((MDB_cursor *)iter->mdb_cur, &k, &v,
   8918 				 (MDB_cursor_op)iter->cursor_op)))
   8919 	{
   8920 		//fprintf(stderr, "autoclosing %d '%s'\n", iter->cursor_op, mdb_strerror(rc));
   8921 		// autoclose
   8922 		ndb_note_relay_iterate_close(iter);
   8923 		return NULL;
   8924 	}
   8925 
   8926 	iter->cursor_op = MDB_NEXT_DUP;
   8927 
   8928 	return (const char*)v.mv_data;
   8929 }
   8930 
   8931 void ndb_note_relay_iterate_close(struct ndb_note_relay_iterator *iter)
   8932 {
   8933 	if (!iter || iter->mdb_cur == NULL)
   8934 		return;
   8935 
   8936 	mdb_cursor_close((MDB_cursor*)iter->mdb_cur);
   8937 
   8938 	iter->mdb_cur = NULL;
   8939 }
   8940 
   8941 void ndb_tags_iterate_start(struct ndb_note *note, struct ndb_iterator *iter)
   8942 {
   8943 	iter->note = note;
   8944 	iter->tag = NULL;
   8945 	iter->index = -1;
   8946 }
   8947 
   8948 // Helper function to get a pointer to the nth tag
   8949 static struct ndb_tag *ndb_tags_tag(struct ndb_tags *tags, size_t index) {
   8950     return (struct ndb_tag *)((uint8_t *)tags + sizeof(struct ndb_tags) + index * sizeof(struct ndb_tag));
   8951 }
   8952 
   8953 int ndb_tags_iterate_next(struct ndb_iterator *iter)
   8954 {
   8955 	struct ndb_tags *tags;
   8956 
   8957 	if (iter->tag == NULL || iter->index == -1) {
   8958 		iter->tag = ndb_tags_tag(&iter->note->tags, 0);
   8959 		iter->index = 0;
   8960 		return iter->note->tags.count != 0;
   8961 	}
   8962 
   8963 	tags = &iter->note->tags;
   8964 
   8965 	if (++iter->index < tags->count) {
   8966 		uint32_t tag_data_size = iter->tag->count * sizeof(iter->tag->strs[0]);
   8967 		iter->tag = (struct ndb_tag *)(iter->tag->strs[0].bytes + tag_data_size);
   8968 		return 1;
   8969 	}
   8970 
   8971 	return 0;
   8972 }
   8973 
   8974 uint16_t ndb_tags_count(struct ndb_tags *tags)
   8975 {
   8976 	return tags->count;
   8977 }
   8978 
   8979 uint16_t ndb_tag_count(struct ndb_tag *tags)
   8980 {
   8981 	return tags->count;
   8982 }
   8983 
   8984 enum ndb_common_kind ndb_kind_to_common_kind(int kind)
   8985 {
   8986 	switch (kind)
   8987 	{
   8988 		case 0:     return NDB_CKIND_PROFILE;
   8989 		case 1:     return NDB_CKIND_TEXT;
   8990 		case 3:     return NDB_CKIND_CONTACTS;
   8991 		case 4:     return NDB_CKIND_DM;
   8992 		case 5:     return NDB_CKIND_DELETE;
   8993 		case 6:     return NDB_CKIND_REPOST;
   8994 		case 7:     return NDB_CKIND_REACTION;
   8995 		case 9735:  return NDB_CKIND_ZAP;
   8996 		case 9734:  return NDB_CKIND_ZAP_REQUEST;
   8997 		case 23194: return NDB_CKIND_NWC_REQUEST;
   8998 		case 23195: return NDB_CKIND_NWC_RESPONSE;
   8999 		case 27235: return NDB_CKIND_HTTP_AUTH;
   9000 		case 30000: return NDB_CKIND_LIST;
   9001 		case 30023: return NDB_CKIND_LONGFORM;
   9002 		case 30315: return NDB_CKIND_STATUS;
   9003 	}
   9004 
   9005 	return -1;
   9006 }
   9007 
   9008 const char *ndb_kind_name(enum ndb_common_kind ck)
   9009 {
   9010 	switch (ck) {
   9011 		case NDB_CKIND_PROFILE:      return "profile";
   9012 		case NDB_CKIND_TEXT:         return "text";
   9013 		case NDB_CKIND_CONTACTS:     return "contacts";
   9014 		case NDB_CKIND_DM:           return "dm";
   9015 		case NDB_CKIND_DELETE:       return "delete";
   9016 		case NDB_CKIND_REPOST:       return "repost";
   9017 		case NDB_CKIND_REACTION:     return "reaction";
   9018 		case NDB_CKIND_ZAP:          return "zap";
   9019 		case NDB_CKIND_ZAP_REQUEST:  return "zap_request";
   9020 		case NDB_CKIND_NWC_REQUEST:  return "nwc_request";
   9021 		case NDB_CKIND_NWC_RESPONSE: return "nwc_response";
   9022 		case NDB_CKIND_HTTP_AUTH:    return "http_auth";
   9023 		case NDB_CKIND_LIST:         return "list";
   9024 		case NDB_CKIND_LONGFORM:     return "longform";
   9025 		case NDB_CKIND_STATUS:       return "status";
   9026 		case NDB_CKIND_COUNT:        return "unknown";
   9027 	}
   9028 
   9029 	return "unknown";
   9030 }
   9031 
   9032 const char *ndb_db_name(enum ndb_dbs db)
   9033 {
   9034 	switch (db) {
   9035 		case NDB_DB_NOTE:
   9036 			return "note";
   9037 		case NDB_DB_META:
   9038 			return "note_metadata";
   9039 		case NDB_DB_PROFILE:
   9040 			return "profile";
   9041 		case NDB_DB_NOTE_ID:
   9042 			return "note_index";
   9043 		case NDB_DB_PROFILE_PK:
   9044 			return "profile_pubkey_index";
   9045 		case NDB_DB_NDB_META:
   9046 			return "nostrdb_metadata";
   9047 		case NDB_DB_PROFILE_SEARCH:
   9048 			return "profile_search";
   9049 		case NDB_DB_PROFILE_LAST_FETCH:
   9050 			return "profile_last_fetch";
   9051 		case NDB_DB_NOTE_KIND:
   9052 			return "note_kind_index";
   9053 		case NDB_DB_NOTE_TEXT:
   9054 			return "note_fulltext";
   9055 		case NDB_DB_NOTE_BLOCKS:
   9056 			return "note_blocks";
   9057 		case NDB_DB_NOTE_TAGS:
   9058 			return "note_tags";
   9059 		case NDB_DB_NOTE_PUBKEY:
   9060 			return "note_pubkey_index";
   9061 		case NDB_DB_NOTE_PUBKEY_KIND:
   9062 			return "note_pubkey_kind_index";
   9063 		case NDB_DB_NOTE_RELAY_KIND:
   9064 			return "note_relay_kind_index";
   9065 		case NDB_DB_NOTE_RELAYS:
   9066 			return "note_relays";
   9067 		case NDB_DBS:
   9068 			return "count";
   9069 	}
   9070 
   9071 	return "unknown";
   9072 }
   9073 
   9074 static struct ndb_blocks *ndb_note_to_blocks(struct ndb_note *note)
   9075 {
   9076 	const char *content;
   9077 	size_t content_len;
   9078 	struct ndb_blocks *blocks;
   9079 	unsigned char *buffer;
   9080 
   9081 	content = ndb_note_content(note);
   9082 	content_len = ndb_note_content_length(note);
   9083 
   9084 	// something weird is going on
   9085 	if (content_len >= INT32_MAX)
   9086 		return NULL;
   9087 
   9088 	buffer = malloc(2<<18);  // Not carefully calculated, but ok because we will not need this once we switch to the local relay model
   9089 	if (!buffer)
   9090 		return NULL;
   9091 
   9092 	if (!ndb_parse_content(buffer, content_len, content, content_len, &blocks)) {
   9093 		free(buffer);
   9094 		return NULL;
   9095 	}
   9096 
   9097 	//blocks = realloc(blocks, ndb_blocks_total_size(blocks));
   9098 	//if (blocks == NULL)
   9099 		//return NULL;
   9100 
   9101 	blocks->flags |= NDB_BLOCK_FLAG_OWNED;
   9102 
   9103 	return blocks;
   9104 }
   9105 
   9106 struct ndb_blocks *ndb_get_blocks_by_key(struct ndb *ndb, struct ndb_txn *txn, uint64_t note_key)
   9107 {
   9108 	struct ndb_blocks *blocks, *blocks_to_writer;
   9109 	size_t blocks_size;
   9110 	struct ndb_note *note;
   9111 	size_t note_len;
   9112 
   9113 	if ((blocks = ndb_lookup_by_key(txn, note_key, NDB_DB_NOTE_BLOCKS, &note_len))) {
   9114 		return blocks;
   9115 	}
   9116 
   9117 	// If we don't have note blocks, let's lazily generate them. This is
   9118 	// migration-friendly instead of doing them all at once
   9119 	if (!(note = ndb_get_note_by_key(txn, note_key, &note_len))) {
   9120 		// no note found, can't return note blocks
   9121 		return NULL;
   9122 	}
   9123 
   9124 	 if (!(blocks = ndb_note_to_blocks(note)))
   9125 		 return NULL;
   9126 
   9127 	 // send a copy to the writer
   9128 	 blocks_size = ndb_blocks_total_size(blocks);
   9129 	 blocks_to_writer = malloc(blocks_size);
   9130 	 memcpy(blocks_to_writer, blocks, blocks_size);
   9131 	 assert(blocks->flags & NDB_BLOCK_FLAG_OWNED);
   9132 
   9133 	 // we generated new blocks, let's store them in the DB
   9134 	 struct ndb_writer_blocks write_blocks = {
   9135 		 .blocks = blocks_to_writer,
   9136 		 .note_key = note_key
   9137 	 };
   9138 
   9139 	 assert(write_blocks.blocks != blocks);
   9140 
   9141 	 struct ndb_writer_msg msg = { .type = NDB_WRITER_BLOCKS };
   9142 	 msg.blocks = write_blocks;
   9143 
   9144 	 ndb_writer_queue_msg(&ndb->writer, &msg);
   9145 
   9146 	 return blocks;
   9147 }
   9148 
   9149 // please call ndb_monitor_lock before calling this
   9150 static struct ndb_subscription *
   9151 ndb_monitor_find_subscription(struct ndb_monitor *monitor, uint64_t subid, int *index)
   9152 {
   9153 	struct ndb_subscription *sub, *tsub;
   9154 	int i;
   9155 
   9156 	for (i = 0, sub = NULL; i < monitor->num_subscriptions; i++) {
   9157 		tsub = &monitor->subscriptions[i];
   9158 		if (tsub->subid == subid) {
   9159 			sub = tsub;
   9160 			if (index)
   9161 				*index = i;
   9162 			break;
   9163 		}
   9164 	}
   9165 
   9166 	return sub;
   9167 }
   9168 
   9169 int ndb_poll_for_notes(struct ndb *ndb, uint64_t subid, uint64_t *note_ids,
   9170 		       int note_id_capacity)
   9171 {
   9172 	struct ndb_subscription *sub;
   9173 	int res;
   9174 
   9175 	if (subid == 0)
   9176 		return 0;
   9177 
   9178 	ndb_monitor_lock(&ndb->monitor);
   9179 
   9180 	if (!(sub = ndb_monitor_find_subscription(&ndb->monitor, subid, NULL)))
   9181 		res = 0;
   9182 	else
   9183 		res = prot_queue_try_pop_all(&sub->inbox, note_ids, note_id_capacity);
   9184 
   9185 	ndb_monitor_unlock(&ndb->monitor);
   9186 
   9187 	return res;
   9188 }
   9189 
   9190 int ndb_wait_for_notes(struct ndb *ndb, uint64_t subid, uint64_t *note_ids,
   9191                        int note_id_capacity)
   9192 {
   9193 	struct ndb_subscription *sub;
   9194 	struct prot_queue *queue_inbox;
   9195 
   9196         // this is not a valid subscription id
   9197 	if (subid == 0)
   9198 		return 0;
   9199 
   9200 	ndb_monitor_lock(&ndb->monitor);
   9201 
   9202         if (!(sub = ndb_monitor_find_subscription(&ndb->monitor, subid, NULL))) {
   9203 		ndb_monitor_unlock(&ndb->monitor);
   9204 		return 0;
   9205 	}
   9206 
   9207 	queue_inbox = &sub->inbox;
   9208 
   9209 	ndb_monitor_unlock(&ndb->monitor);
   9210 
   9211 	// there is technically a race condition if the thread yeilds at this
   9212 	// comment and a subscription is added/removed. A deadlock in the
   9213 	// writer queue would be much worse though. This function is dubious
   9214 	// anyways.
   9215 
   9216         return prot_queue_pop_all(queue_inbox, note_ids, note_id_capacity);
   9217 }
   9218 
   9219 int ndb_unsubscribe(struct ndb *ndb, uint64_t subid)
   9220 {
   9221 	struct ndb_subscription *sub;
   9222 	int index, res, elems_to_move;
   9223 
   9224 	ndb_monitor_lock(&ndb->monitor);
   9225 
   9226 	if (!(sub = ndb_monitor_find_subscription(&ndb->monitor, subid, &index))) {
   9227 		res = 0;
   9228 		goto done;
   9229 	}
   9230 
   9231 	ndb_subscription_destroy(sub);
   9232 
   9233 	elems_to_move = (--ndb->monitor.num_subscriptions) - index;
   9234 
   9235 	memmove(&ndb->monitor.subscriptions[index],
   9236 		&ndb->monitor.subscriptions[index+1],
   9237 		elems_to_move * sizeof(*sub));
   9238 
   9239 	res = 1;
   9240 
   9241 done:
   9242 	ndb_monitor_unlock(&ndb->monitor);
   9243 
   9244 	return res;
   9245 }
   9246 
   9247 int ndb_num_subscriptions(struct ndb *ndb)
   9248 {
   9249 	return ndb->monitor.num_subscriptions;
   9250 }
   9251 
   9252 uint64_t ndb_subscribe(struct ndb *ndb, struct ndb_filter *filters, int num_filters)
   9253 {
   9254 	static uint64_t subids = 0;
   9255 	struct ndb_subscription *sub;
   9256 	size_t buflen;
   9257 	uint64_t subid;
   9258 	char *buf;
   9259 
   9260 	ndb_monitor_lock(&ndb->monitor);
   9261 
   9262 	if (ndb->monitor.num_subscriptions + 1 >= MAX_SUBSCRIPTIONS) {
   9263 		fprintf(stderr, "too many subscriptions\n");
   9264 		subid = 0;
   9265 		goto done;
   9266 	}
   9267 
   9268 	sub = &ndb->monitor.subscriptions[ndb->monitor.num_subscriptions];
   9269 	subid = ++subids;
   9270 	sub->subid = subid;
   9271 
   9272 	ndb_filter_group_init(&sub->group);
   9273 	if (!ndb_filter_group_add_filters(&sub->group, filters, num_filters)) {
   9274 		subid = 0;
   9275 		goto done;
   9276 	}
   9277 
   9278 	// 500k ought to be enough for anyone
   9279 	buflen = sizeof(uint64_t) * DEFAULT_QUEUE_SIZE;
   9280 	buf = malloc(buflen);
   9281 
   9282 	if (!prot_queue_init(&sub->inbox, buf, buflen, sizeof(uint64_t))) {
   9283 		fprintf(stderr, "failed to push prot queue\n");
   9284 		subid = 0;
   9285 		goto done;
   9286 	}
   9287 
   9288 	ndb->monitor.num_subscriptions++;
   9289 done:
   9290 	ndb_monitor_unlock(&ndb->monitor);
   9291 
   9292 	return subid;
   9293 }