nostrdb

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

test.c (74330B)


      1 
      2 #include "nostrdb.h"
      3 #include "hex.h"
      4 #include "io.h"
      5 #include "bolt11/bolt11.h"
      6 #include "invoice.h"
      7 #include "block.h"
      8 #include "protected_queue.h"
      9 #include "memchr.h"
     10 #include "print_util.h"
     11 #include "bindings/c/profile_reader.h"
     12 #include "bindings/c/profile_verifier.h"
     13 #include "bindings/c/meta_reader.h"
     14 #include "bindings/c/meta_verifier.h"
     15 
     16 #include <stdio.h>
     17 #include <assert.h>
     18 #include <unistd.h>
     19 #include <sys/stat.h>
     20 
     21 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
     22 
     23 int ndb_print_kind_keys(struct ndb_txn *txn);
     24 #define TEST_DIR "./testdata/db"
     25 static const char *test_dir = TEST_DIR;
     26 
     27 static void delete_test_db() {
     28     // Delete ./testdata/db/data.mdb
     29     unlink(TEST_DIR "/data.mdb");
     30     unlink(TEST_DIR "/data.lock");
     31 }
     32 
     33 int ndb_rebuild_reaction_metadata(struct ndb_txn *txn, const unsigned char *note_id, struct ndb_note_meta_builder *builder, uint32_t *count);
     34 int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies);
     35 
     36 static void db_load_events(struct ndb *ndb, const char *filename)
     37 {
     38 	size_t filesize;
     39 	int written;
     40 	char *json;
     41 	struct stat st;
     42 
     43 	stat(filename, &st);
     44 	filesize = st.st_size;
     45 
     46 	json = malloc(filesize + 1); // +1 for '\0' if you need it null-terminated
     47 	read_file(filename, (unsigned char*)json, filesize, &written);
     48 	assert(ndb_process_client_events(ndb, json, written));
     49 	free(json);
     50 }
     51 
     52 static NdbProfile_table_t lookup_profile(struct ndb_txn *txn, uint64_t pk)
     53 {
     54 	void *root;
     55 	size_t len;
     56 	assert((root = ndb_get_profile_by_key(txn, pk, &len)));
     57 	assert(root);
     58 
     59 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(root);
     60 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
     61 	return profile;
     62 }
     63 
     64 static void print_search(struct ndb_txn *txn, struct ndb_search *search)
     65 {
     66 	NdbProfile_table_t profile = lookup_profile(txn, search->profile_key);
     67 	const char *name = NdbProfile_name_get(profile);
     68 	const char *display_name = NdbProfile_display_name_get(profile);
     69 	printf("searched_name name:'%s' display_name:'%s' pk:%" PRIu64 " ts:%" PRIu64 " id:", name, display_name, search->profile_key, search->key->timestamp);
     70 	print_hex(search->key->id, 32);
     71 	printf("\n");
     72 }
     73 
     74 static void test_count_metadata()
     75 {
     76 	struct ndb *ndb;
     77 	struct ndb_config config;
     78 	struct ndb_txn txn;
     79 	struct ndb_note_meta *meta;
     80 	//struct ndb_note_meta_entry *counts;
     81 	struct ndb_note_meta_entry *entry;
     82 	uint16_t count, direct_replies[2];
     83 	uint32_t total_reactions, reactions, thread_replies[2];
     84 	int i;
     85 
     86 	reactions = 0;
     87 	delete_test_db();
     88 
     89 	ndb_default_config(&config);
     90 	assert(ndb_init(&ndb, test_dir, &config));
     91 
     92 	const unsigned char id[] = {
     93 		0xd4, 0x4a, 0xd9, 0x6c, 0xb8, 0x92, 0x40, 0x92, 0xa7, 0x6b, 0xc2, 0xaf,
     94 		0xdd, 0xeb, 0x12, 0xeb, 0x85, 0x23, 0x3c, 0x0d, 0x03, 0xa7, 0xd9, 0xad,
     95 		0xc4, 0x2c, 0x2a, 0x85, 0xa7, 0x9a, 0x43, 0x05
     96 	};
     97 
     98 	db_load_events(ndb, "testdata/test_counts.json");
     99 
    100 	/* consume all events to ensure we're done processing */
    101 	ndb_destroy(ndb);
    102 	ndb_init(&ndb, test_dir, &config);
    103 
    104 	ndb_begin_query(ndb, &txn);
    105 	meta = ndb_get_note_meta(&txn, id);
    106 	assert(meta);
    107 
    108 	count = ndb_note_meta_entries_count(meta);
    109 	entry = ndb_note_meta_entries(meta);
    110 	for (i = 0; i < count; i++) {
    111 		entry = ndb_note_meta_entry_at(meta, i);
    112 		if (*ndb_note_meta_entry_type(entry) == NDB_NOTE_META_REACTION) {
    113 			reactions += *ndb_note_meta_reaction_count(entry);
    114 		}
    115 	}
    116 
    117 	entry = ndb_note_meta_find_entry(meta, NDB_NOTE_META_COUNTS, NULL);
    118 
    119 	assert(entry);
    120 	assert(*ndb_note_meta_counts_quotes(entry) == 2);
    121 
    122 	thread_replies[0] = *ndb_note_meta_counts_thread_replies(entry);
    123 	printf("\t# thread replies %d\n", thread_replies[0]);
    124 	assert(thread_replies[0] == 93);
    125 
    126 	direct_replies[0] = *ndb_note_meta_counts_direct_replies(entry);
    127 	printf("\t# direct replies %d\n", direct_replies[0]);
    128 	assert(direct_replies[0] == 83);
    129 
    130 	total_reactions = *ndb_note_meta_counts_total_reactions(entry);
    131 	printf("\t# total reactions %d\n", reactions);
    132 	assert(total_reactions > 0);
    133 
    134 	printf("\t# reactions %d\n", reactions);
    135 	assert(reactions > 0);
    136 	assert(total_reactions == reactions);
    137 
    138 
    139 	ndb_end_query(&txn);
    140 
    141 	ndb_begin_query(ndb, &txn);
    142 	/* this is used in the migration code,
    143 	 * let's make sure it matches the online logic */
    144 	ndb_rebuild_reaction_metadata(&txn, id, NULL, &reactions);
    145 	printf("\t# after-counted reactions %d\n", reactions);
    146 	assert(reactions == total_reactions);
    147 
    148 	ndb_count_replies(&txn, id, &direct_replies[1], &thread_replies[1]);
    149 	printf("\t# after-counted replies direct:%d thread:%d\n", direct_replies[1], thread_replies[1]);
    150 	assert(direct_replies[0] == direct_replies[1]);
    151 	assert(thread_replies[0] == thread_replies[1]);
    152 
    153 	ndb_end_query(&txn);
    154 
    155 	ndb_destroy(ndb);
    156 	delete_test_db();
    157 
    158 	printf("ok test_count_metadata\n");
    159 }
    160 
    161 static void test_metadata()
    162 {
    163 	unsigned char buffer[1024];
    164 	union ndb_reaction_str str;
    165 	struct ndb_note_meta_builder builder;
    166 	struct ndb_note_meta *meta;
    167 	struct ndb_note_meta_entry *entry = NULL;
    168 	int ok;
    169 
    170 	ok = ndb_note_meta_builder_init(&builder, buffer, sizeof(buffer));
    171 	assert(ok);
    172 
    173 	entry = ndb_note_meta_add_entry(&builder);
    174 	assert(entry);
    175 
    176 	ndb_reaction_set(&str, "πŸ΄β€β˜ οΈ");
    177 	ndb_note_meta_reaction_set(entry, 1337, str);
    178 
    179 	ndb_note_meta_build(&builder, &meta);
    180 
    181 	assert(ndb_note_meta_entries_count(meta) == 1);
    182 	assert(ndb_note_meta_total_size(meta) == 32);
    183 
    184 	entry = ndb_note_meta_entries(meta);
    185 	assert(*ndb_note_meta_reaction_count(entry) == 1337);
    186 
    187 	printf("ok test_metadata\n");
    188 }
    189 
    190 static void test_reaction_encoding()
    191 {
    192 	union ndb_reaction_str reaction;
    193 	assert(ndb_reaction_set(&reaction, "πŸ‘©πŸ»β€πŸ€β€πŸ‘©πŸΏ"));
    194 	assert(reaction.binmoji == 0x07D1A7747240B0D0);
    195 	assert(ndb_reaction_str_is_emoji(reaction) == 1);
    196 	assert(ndb_reaction_set(&reaction, "hello"));
    197 	assert(ndb_reaction_str_is_emoji(reaction) == 0);
    198 	assert(!strcmp(reaction.packed.str, "hello"));
    199 	printf("ok test_reaction_encoding\n");
    200 }
    201 
    202 
    203 static void test_filters()
    204 {
    205 	struct ndb_filter filter, *f;
    206 	struct ndb_filter_elements *current;
    207 	struct ndb_note *note;
    208 	unsigned char buffer[4096];
    209 
    210 	const char *test_note = "{\"id\": \"160e76ca67405d7ce9ef7d2dd72f3f36401c8661a73d45498af842d40b01b736\",\"pubkey\": \"67c67870aebc327eb2a2e765e6dbb42f0f120d2c4e4e28dc16b824cf72a5acc1\",\"created_at\": 1700688516,\"kind\": 1337,\"tags\": [[\"t\",\"hashtag\"],[\"t\",\"grownostr\"],[\"p\",\"4d2e7a6a8e08007ace5a03391d21735f45caf1bf3d67b492adc28967ab46525e\"]],\"content\": \"\",\"sig\": \"20c2d070261ed269559ada40ca5ac395c389681ee3b5f7d50de19dd9b328dd70cf27d9d13875e87c968d9b49fa05f66e90f18037be4529b9e582c7e2afac3f06\"}";
    211 
    212 	f = &filter;
    213 	assert(ndb_note_from_json(test_note, strlen(test_note), &note, buffer, sizeof(buffer)));
    214 
    215 	assert(ndb_filter_init(f));
    216 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
    217 	assert(ndb_filter_add_int_element(f, 1337));
    218 	assert(ndb_filter_add_int_element(f, 2));
    219 
    220 	current = ndb_filter_current_element(f);
    221 	assert(current->count == 2);
    222 	assert(current->field.type == NDB_FILTER_KINDS);
    223 
    224 	// can't start if we've already started
    225 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS) == 0);
    226 	assert(ndb_filter_start_field(f, NDB_FILTER_TAGS) == 0);
    227 	ndb_filter_end_field(f);
    228 
    229 	// should be sorted after end
    230 	assert(current->elements[0] == 2);
    231 	assert(current->elements[1] == 1337);
    232 
    233 	ndb_filter_end(f);
    234 
    235 	// try matching the filter
    236 	assert(ndb_filter_matches(f, note));
    237 
    238 	_ndb_note_set_kind(note, 1);
    239 
    240 	// inverse match
    241 	assert(!ndb_filter_matches(f, note));
    242 
    243 	// should also match 2
    244 	_ndb_note_set_kind(note, 2);
    245 	assert(ndb_filter_matches(f, note));
    246 
    247 	// don't free, just reset data pointers
    248 	ndb_filter_destroy(f);
    249 	ndb_filter_init(f);
    250 
    251 	// now try generic matches
    252 	assert(ndb_filter_start_tag_field(f, 't'));
    253 	assert(ndb_filter_add_str_element(f, "grownostr"));
    254 	ndb_filter_end_field(f);
    255 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
    256 	assert(ndb_filter_add_int_element(f, 3));
    257 	ndb_filter_end_field(f);
    258 	ndb_filter_end(f);
    259 
    260 	// shouldn't match the kind filter
    261 	assert(!ndb_filter_matches(f, note));
    262 
    263 	_ndb_note_set_kind(note, 3);
    264 
    265 	// now it should
    266 	assert(ndb_filter_matches(f, note));
    267 
    268 	ndb_filter_destroy(f);
    269 	ndb_filter_init(f);
    270 	assert(ndb_filter_start_field(f, NDB_FILTER_AUTHORS));
    271 	assert(ndb_filter_add_id_element(f, ndb_note_pubkey(note)));
    272 	ndb_filter_end_field(f);
    273 	ndb_filter_end(f);
    274 	assert(f->current == -1);
    275 	assert(ndb_filter_matches(f, note));
    276 
    277 	ndb_filter_destroy(f);
    278 }
    279 
    280 static void test_filter_json()
    281 {
    282 	struct ndb_filter filter, *f = &filter;
    283 	char buf[1024];
    284 
    285 	unsigned char pk[32] = { 0x32, 0xe1, 0x82, 0x76, 0x35, 0x45, 0x0e, 0xbb, 0x3c, 0x5a, 0x7d, 0x12, 0xc1, 0xf8, 0xe7, 0xb2, 0xb5, 0x14, 0x43, 0x9a, 0xc1, 0x0a, 0x67, 0xee, 0xf3, 0xd9, 0xfd, 0x9c, 0x5c, 0x68, 0xe2, 0x45 };
    286 
    287 	unsigned char pk2[32] = {
    288 		0xd1, 0x2c, 0x17, 0xbd, 0xe3, 0x09, 0x4a, 0xd3, 0x2f, 0x4a, 0xb8, 0x62, 0xa6, 0xcc, 0x6f, 0x5c, 0x28, 0x9c, 0xfe, 0x7d, 0x58, 0x02, 0x27, 0x0b, 0xdf, 0x34, 0x90, 0x4d, 0xf5, 0x85, 0xf3, 0x49
    289 	};
    290 
    291 	ndb_filter_init(f);
    292 	assert(ndb_filter_start_field(f, NDB_FILTER_UNTIL));
    293 	assert(ndb_filter_add_int_element(f, 42));
    294 	ndb_filter_end_field(f);
    295 	ndb_filter_end(f);
    296 	assert(ndb_filter_json(f, buf, sizeof(buf)));
    297 	assert(!strcmp("{\"until\":42}", buf));
    298 	ndb_filter_destroy(f);
    299 
    300 	ndb_filter_init(f);
    301 	assert(ndb_filter_start_field(f, NDB_FILTER_UNTIL));
    302 	assert(ndb_filter_add_int_element(f, 42));
    303 	ndb_filter_end_field(f);
    304 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
    305 	assert(ndb_filter_add_int_element(f, 1));
    306 	assert(ndb_filter_add_int_element(f, 2));
    307 	ndb_filter_end_field(f);
    308 	ndb_filter_end(f);
    309 	assert(ndb_filter_json(f, buf, sizeof(buf)));
    310 	assert(!strcmp("{\"until\":42,\"kinds\":[1,2]}", buf));
    311 	ndb_filter_destroy(f);
    312 
    313 	ndb_filter_init(f);
    314 	assert(ndb_filter_start_field(f, NDB_FILTER_IDS));
    315 	assert(ndb_filter_add_id_element(f, pk));
    316 	assert(ndb_filter_add_id_element(f, pk2));
    317 	ndb_filter_end_field(f);
    318 	ndb_filter_end(f);
    319 	assert(ndb_filter_json(f, buf, sizeof(buf)));
    320 	assert(!strcmp("{\"ids\":[\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"d12c17bde3094ad32f4ab862a6cc6f5c289cfe7d5802270bdf34904df585f349\"]}", buf));
    321 	ndb_filter_destroy(f);
    322 }
    323 
    324 static void test_invoice_encoding(const char *bolt11_str)
    325 {
    326 	unsigned char buf[4096];
    327 	char *fail = NULL;
    328 	struct cursor cur;
    329 	struct ndb_invoice invoice;
    330 	struct bolt11 *bolt11;
    331 
    332 	bolt11 = bolt11_decode_minimal(NULL, bolt11_str, &fail);
    333 	make_cursor(buf, buf + sizeof(buf), &cur);
    334 
    335 	if (fail != NULL) {
    336 		printf("invoice decoding failed: %s\n", fail);
    337 	}
    338 	assert(fail == NULL);
    339 	assert(ndb_encode_invoice(&cur, bolt11));
    340 	cur.p = cur.start;
    341 	assert(ndb_decode_invoice(&cur, &invoice));
    342 
    343 	assert(bolt11->msat->millisatoshis == invoice.amount);
    344 	assert(bolt11->timestamp == invoice.timestamp);
    345 	assert(bolt11->expiry == invoice.expiry);
    346 
    347 	if (bolt11->description != NULL && invoice.description != NULL)
    348 		assert(!strcmp(bolt11->description, invoice.description));
    349 	else if (bolt11->description_hash != NULL && invoice.description_hash != NULL)
    350 		assert(!memcmp(bolt11->description_hash->u.u8, invoice.description_hash, 32));
    351 	else
    352 		assert(0);
    353 	
    354 	tal_free(bolt11);
    355 }
    356 
    357 static void test_encode_decode_invoice()
    358 {
    359 	const char *deschash = "lnbc12n1pjctuljsp57l6za0xry37prkrz7vuv4324ljnssm8ukr2vrf6qvvrgclsmpyhspp5xqfuk89duzjlt2yg56ym7p3enrfxxltyfpc364qc8nsu3kznkl8shp5eugmd894yph7wq68u09gke5x2hmn7mg3zrwd06fs57gmcrjm0uxsxqyjw5qcqpjrzjqd7yw3w4kvhx8uvcj7qusfw4uqre3j56zjz9t07nd2u55yuya3awsrqdlcqqdzcqqqqqqqqqqqqqqzqqyg9qxpqysgqwm2tsc448ellvf5xem2c95hfvc07lakph9r8hffh704uxqhs22r9s4ly0jel48zv6f7fy8zjkgmjt5h2l4jc9gyj4av42s40qvve2ysqwuega8";
    360 	const char *desc = "lnbc12u1pjctuklsp5lg8wdhq2g5xfphkqd5k6gf0femt06wfevu94uuqfprc4ggyqma7spp54lmpmz0mhv3lczepdckr0acf3gdany2654u4k2s8fp5xh0yanjhsdq5w3jhxapdd9h8vmmfvdjsxqyjw5qcqpjrzjqgtsq68q0s9wdadpg32gcfu7hslgkhdpaysj2ha3dtnm8882wa6jyzahpqqqpsgqqyqqqqlgqqqqqpsq9q9qxpqysgqdqzhl8gz46nmalhg27stl25z2u7mqtclv3zz223mjwut90m24fa46xqprjewsqys78j2uljfznz5vtefctu6fw7375ee66e62tj965gpcs85tc";
    361 	const char *problem = "lnbc1230n1p5fetpfpp5mqn7v09jz8pkxl67h4hgd8z2xuqfzfhlw0d4yu5dz4z35ermszaqdq57z0cadhsn78tduylnztscqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5mhdv3kgh8y57hd0nezqk0yqhdtkjecnykfxer2k4geg7x34xvqyq9qxpqysgqylpwwyjlvfhc4jzw5hl77a5ajdf7ay6hku7vpznc9efe8nw0h2jp58p7hl2km3hsf3k40z6tey4ye26zf3wwt77ws02rdzzl3cem97squshha0";
    362 
    363 	test_invoice_encoding(deschash);
    364 	test_invoice_encoding(desc);
    365 	test_invoice_encoding(problem);
    366 }
    367 
    368 // Test the created_at query plan via a contact-list query
    369 static void test_timeline_query()
    370 {
    371 	struct ndb *ndb;
    372 	struct ndb_filter filter;
    373 	struct ndb_config config;
    374 	struct ndb_txn txn;
    375 	struct ndb_query_result results[10];
    376 	int count;
    377 	ndb_default_config(&config);
    378 
    379 	assert(ndb_init(&ndb, test_dir, &config));
    380 
    381 	ndb_filter_init(&filter);
    382 	ndb_filter_start_field(&filter, NDB_FILTER_AUTHORS);
    383 #include "testdata/author-filter.c"
    384 	ndb_filter_end_field(&filter);
    385 	ndb_filter_end(&filter);
    386 
    387 	ndb_begin_query(ndb, &txn);
    388 	assert(ndb_query(&txn, &filter, 1, results,
    389 			 sizeof(results)/sizeof(results[0]), &count));
    390 	ndb_end_query(&txn);
    391 	ndb_filter_destroy(&filter);
    392 
    393 	assert(count == 10);
    394 }
    395 
    396 // Test fetched_at profile records. These are saved when new profiles are
    397 // processed, or the last time we've fetched the profile.
    398 static void test_fetched_at()
    399 {
    400 	struct ndb *ndb;
    401 	struct ndb_txn txn;
    402 	uint64_t fetched_at, t1, t2;
    403 	struct ndb_config config;
    404 	ndb_default_config(&config);
    405 
    406 	assert(ndb_init(&ndb, test_dir, &config));
    407 
    408 	const unsigned char pubkey[] = { 0x87, 0xfb, 0xc6, 0xd5, 0x98, 0x31, 0xa8, 0x23, 0xa4, 0x5d, 0x10, 0x1f,
    409   0x86, 0x94, 0x2c, 0x41, 0xcd, 0xe2, 0x90, 0x23, 0xf4, 0x09, 0x20, 0x24,
    410   0xa2, 0x7c, 0x50, 0x10, 0x3c, 0x15, 0x40, 0x01 };
    411 
    412 	const char profile_1[] = "[\"EVENT\",{\"id\": \"a44eb8fb6931d6155b04038bef0624407e46c85c61e5758392cbb615f00184ca\",\"pubkey\": \"87fbc6d59831a823a45d101f86942c41cde29023f4092024a27c50103c154001\",\"created_at\": 1695593354,\"kind\": 0,\"tags\": [],\"content\": \"{\\\"name\\\":\\\"b\\\"}\",\"sig\": \"7540bbde4b4479275e20d95acaa64027359a73989927f878825093cba2f468bd8e195919a77b4c230acecddf92e6b4bee26918b0c0842f84ec7c1fae82453906\"}]";
    413 
    414 	t1 = time(NULL);
    415 
    416 	// process the first event, this should set the fetched_at
    417 	assert(ndb_process_client_event(ndb, profile_1, sizeof(profile_1)));
    418 
    419 	// we sleep for a second because we want to make sure the fetched_at is not
    420 	// updated for the next record, which is an older profile.
    421 	sleep(1);
    422 
    423 	assert(ndb_begin_query(ndb, &txn));
    424 
    425 	// this should be set to t1
    426 	fetched_at = ndb_read_last_profile_fetch(&txn, pubkey);
    427 
    428 	if (fetched_at != t1) {
    429 		printf("fetched_at != t1? %" PRIu64 " != %" PRIu64 "\n", fetched_at, t1);
    430 	}
    431 	assert(fetched_at == t1);
    432 
    433 	t2 = time(NULL);
    434 	assert(t1 != t2); // sanity
    435 
    436 	const char profile_2[] = "[\"EVENT\",{\"id\": \"9b2861dda8fc602ec2753f92f1a443c9565de606e0c8f4fd2db4f2506a3b13ca\",\"pubkey\": \"87fbc6d59831a823a45d101f86942c41cde29023f4092024a27c50103c154001\",\"created_at\": 1695593347,\"kind\": 0,\"tags\": [],\"content\": \"{\\\"name\\\":\\\"a\\\"}\",\"sig\": \"f48da228f8967d33c3caf0a78f853b5144631eb86c7777fd25949123a5272a92765a0963d4686dd0efe05b7a9b986bfac8d43070b234153acbae5006d5a90f31\"}]";
    437 
    438 	t2 = time(NULL);
    439 
    440 	// process the second event, since this is older it should not change
    441 	// fetched_at
    442 	assert(ndb_process_client_event(ndb, profile_2, sizeof(profile_2)));
    443 
    444 	// we sleep for a second because we want to make sure the fetched_at is not
    445 	// updated for the next record, which is an older profile.
    446 	sleep(1);
    447 
    448 	fetched_at = ndb_read_last_profile_fetch(&txn, pubkey);
    449 	assert(fetched_at == t1);
    450 
    451 	ndb_end_query(&txn);
    452 	ndb_destroy(ndb);
    453 }
    454 
    455 static void test_reaction_counter()
    456 {
    457 	struct ndb *ndb;
    458 	int reactions, results;
    459 	struct ndb_txn txn;
    460 	struct ndb_note_meta_entry *entry;
    461 	struct ndb_note_meta *meta;
    462 	struct ndb_config config;
    463 	ndb_default_config(&config);
    464 	static const int num_reactions = 3;
    465 	uint64_t note_ids[num_reactions], subid;
    466 	union ndb_reaction_str str;
    467 
    468 	assert(ndb_init(&ndb, test_dir, &config));
    469 	assert((subid = ndb_subscribe(ndb, NULL, 0)));
    470 
    471 	db_load_events(ndb, "testdata/reactions.json");
    472 
    473 	for (reactions = 0; reactions < num_reactions;) {
    474 		results = ndb_wait_for_notes(ndb, subid, note_ids, num_reactions);
    475 		reactions += results;
    476 		assert(reactions > 0);
    477 	}
    478 
    479 	assert(ndb_begin_query(ndb, &txn));
    480 
    481 	const unsigned char id[32] = {
    482 	  0x1a, 0x41, 0x56, 0x30, 0x31, 0x09, 0xbb, 0x4a, 0x66, 0x0a, 0x6a, 0x90,
    483 	  0x04, 0xb0, 0xcd, 0xce, 0x8d, 0x83, 0xc3, 0x99, 0x1d, 0xe7, 0x86, 0x4f,
    484 	  0x18, 0x76, 0xeb, 0x0f, 0x62, 0x2c, 0x68, 0xe8
    485 	};
    486 
    487 	assert((meta = ndb_get_note_meta(&txn, id)));
    488 	ndb_reaction_set(&str, "+");
    489 	entry = ndb_note_meta_find_entry(meta, NDB_NOTE_META_REACTION, &str.binmoji);
    490 	assert(entry);
    491 	//printf("+ count %d\n", *ndb_note_meta_reaction_count(entry));
    492 	assert(*ndb_note_meta_reaction_count(entry) == 1);
    493 	ndb_reaction_set(&str, "-");
    494 	entry = ndb_note_meta_find_entry(meta, NDB_NOTE_META_REACTION, &str.binmoji);
    495 	assert(entry);
    496 	assert(*ndb_note_meta_reaction_count(entry) == 1);
    497 	ndb_end_query(&txn);
    498 	ndb_destroy(ndb);
    499 	delete_test_db();
    500 }
    501 
    502 static void test_profile_search(struct ndb *ndb)
    503 {
    504 	struct ndb_txn txn;
    505 	struct ndb_search search;
    506 	int i;
    507 	const char *name;
    508 	NdbProfile_table_t profile;
    509 
    510 	assert(ndb_begin_query(ndb, &txn));
    511 	assert(ndb_search_profile(&txn, &search, "jean"));
    512 	//print_search(&txn, &search);
    513 	profile = lookup_profile(&txn, search.profile_key);
    514 	name = NdbProfile_name_get(profile);
    515 	assert(!strncmp(name, "jean", 4));
    516 
    517 	assert(ndb_search_profile_next(&search));
    518 	//print_search(&txn, &search);
    519 	profile = lookup_profile(&txn, search.profile_key);
    520 	name = NdbProfile_name_get(profile);
    521 	//assert(strncmp(name, "jean", 4));
    522 
    523 	for (i = 0; i < 3; i++) {
    524 		ndb_search_profile_next(&search);
    525 		//print_search(&txn, &search);
    526 	}
    527 
    528 	//assert(!strcmp(name, "jb55"));
    529 
    530 	ndb_search_profile_end(&search);
    531 	ndb_end_query(&txn);
    532 }
    533 
    534 static void test_profile_updates()
    535 {
    536 	static const int alloc_size = 1024 * 1024;
    537 	static const int num_notes = 3;
    538 	char *json;
    539 	int written, i;
    540 	size_t len;
    541 	struct ndb *ndb;
    542 	struct ndb_config config;
    543 	struct ndb_txn txn;
    544 	uint64_t key, subid;
    545 	uint64_t note_ids[num_notes];
    546 	void *record;
    547 
    548 	json = malloc(alloc_size);
    549 
    550 	ndb_default_config(&config);
    551 	assert(ndb_init(&ndb, test_dir, &config));
    552 
    553 	subid = ndb_subscribe(ndb, NULL, 0);
    554 
    555 	ndb_debug("testing profile updates\n");
    556 	read_file("testdata/profile-updates.json", (unsigned char*)json, alloc_size, &written);
    557 	assert(ndb_process_client_events(ndb, json, written));
    558 
    559 	for (i = 0; i < num_notes;)
    560 		i += ndb_wait_for_notes(ndb, subid, note_ids, num_notes);
    561 
    562 	assert(ndb_begin_query(ndb, &txn));
    563 	const unsigned char pk[32] = {
    564 		0x1c, 0x55, 0x46, 0xe4, 0xf5, 0x93, 0x3b, 0xbe, 0x86, 0x66,
    565 		0x2a, 0x8e, 0xc3, 0x28, 0x9a, 0x29, 0x87, 0xc0, 0x5d, 0xab,
    566 		0x25, 0x6c, 0x06, 0x8b, 0x77, 0x42, 0x9f, 0x0f, 0x08, 0xa7,
    567 		0xa0, 0x90
    568 	};
    569 	record = ndb_get_profile_by_pubkey(&txn, pk, &len, &key);
    570 
    571 	assert(record);
    572 	int res = NdbProfileRecord_verify_as_root(record, len);
    573 	assert(res == 0);
    574 
    575 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(record);
    576 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
    577 	const char *name = NdbProfile_name_get(profile);
    578 
    579 	assert(!strcmp(name, "c"));
    580 
    581 	ndb_end_query(&txn);
    582 	ndb_destroy(ndb);
    583 	free(json);
    584 }
    585 
    586 static void test_load_profiles()
    587 {
    588 	static const int alloc_size = 1024 * 1024;
    589 	char *json = malloc(alloc_size);
    590 	struct ndb *ndb;
    591 	int written;
    592 	struct ndb_config config;
    593 	ndb_default_config(&config);
    594 
    595 	assert(ndb_init(&ndb, test_dir, &config));
    596 
    597 	read_file("testdata/profiles.json", (unsigned char*)json, alloc_size, &written);
    598 
    599 	assert(ndb_process_events(ndb, json, written));
    600 
    601 	ndb_destroy(ndb);
    602 
    603 	assert(ndb_init(&ndb, test_dir, &config));
    604 	unsigned char id[32] = {
    605 	  0x22, 0x05, 0x0b, 0x6d, 0x97, 0xbb, 0x9d, 0xa0, 0x9e, 0x90, 0xed, 0x0c,
    606 	  0x6d, 0xd9, 0x5e, 0xed, 0x1d, 0x42, 0x3e, 0x27, 0xd5, 0xcb, 0xa5, 0x94,
    607 	  0xd2, 0xb4, 0xd1, 0x3a, 0x55, 0x43, 0x09, 0x07 };
    608 	const char *expected_content = "{\"website\":\"selenejin.com\",\"lud06\":\"\",\"nip05\":\"selenejin@BitcoinNostr.com\",\"picture\":\"https://nostr.build/i/3549697beda0fe1f4ae621f359c639373d92b7c8d5c62582b656c5843138c9ed.jpg\",\"display_name\":\"Selene Jin\",\"about\":\"INTJ | Founding Designer @Blockstream\",\"name\":\"SeleneJin\"}";
    609 
    610 	struct ndb_txn txn;
    611 	assert(ndb_begin_query(ndb, &txn));
    612 	struct ndb_note *note = ndb_get_note_by_id(&txn, id, NULL, NULL);
    613 	assert(note != NULL);
    614 	assert(!strcmp(ndb_note_content(note), expected_content));
    615 	ndb_end_query(&txn);
    616 
    617 	test_profile_search(ndb);
    618 
    619 	ndb_destroy(ndb);
    620 
    621 	free(json);
    622 }
    623 
    624 static void test_fuzz_events() {
    625 	struct ndb *ndb;
    626 	const char *str = "[\"EVENT\"\"\"{\"content\"\"created_at\":0 \"id\"\"5086a8f76fe1da7fb56a25d1bebbafd70fca62e36a72c6263f900ff49b8f8604\"\"kind\":0 \"pubkey\":9c87f94bcbe2a837adc28d46c34eeaab8fc2e1cdf94fe19d4b99ae6a5e6acedc \"sig\"\"27374975879c94658412469cee6db73d538971d21a7b580726a407329a4cafc677fb56b946994cea59c3d9e118fef27e4e61de9d2c46ac0a65df14153 ea93cf5\"\"tags\"[[][\"\"]]}]";
    627 	struct ndb_config config;
    628 	ndb_default_config(&config);
    629 
    630 	ndb_init(&ndb, test_dir, &config);
    631 	ndb_process_event(ndb, str, strlen(str));
    632 	ndb_destroy(ndb);
    633 }
    634 
    635 static void test_migrate() {
    636 	static const char *v0_dir = "testdata/db/v0";
    637 	struct ndb *ndb;
    638 	struct ndb_config config;
    639 	struct ndb_txn txn;
    640 
    641 	ndb_default_config(&config);
    642 	ndb_config_set_flags(&config, NDB_FLAG_NOMIGRATE);
    643 
    644 	fprintf(stderr, "testing migrate on v0\n");
    645 	assert(ndb_init(&ndb, v0_dir, &config));
    646 	assert(ndb_begin_query(ndb, &txn));
    647 	assert(ndb_db_version(&txn) == 0);
    648 	assert(ndb_end_query(&txn));
    649 	ndb_destroy(ndb);
    650 
    651 	ndb_config_set_flags(&config, 0);
    652 
    653 	assert(ndb_init(&ndb, v0_dir, &config));
    654 	ndb_destroy(ndb);
    655 	assert(ndb_init(&ndb, v0_dir, &config));
    656 
    657 	assert(ndb_begin_query(ndb, &txn));
    658 	assert(ndb_db_version(&txn) == 3);
    659 	assert(ndb_end_query(&txn));
    660 
    661 	test_profile_search(ndb);
    662 	ndb_destroy(ndb);
    663 }
    664 
    665 static void test_basic_event() {
    666 	unsigned char buf[512];
    667 	struct ndb_builder builder, *b = &builder;
    668 	struct ndb_note *note;
    669 	int ok;
    670 
    671 	unsigned char id[32];
    672 	memset(id, 1, 32);
    673 
    674 	unsigned char pubkey[32];
    675 	memset(pubkey, 2, 32);
    676 
    677 	unsigned char sig[64];
    678 	memset(sig, 3, 64);
    679 
    680 	const char *hex_pk = "5d9b81b2d4d5609c5565286fc3b511dc6b9a1b3d7d1174310c624d61d1f82bb9";
    681 
    682 	ok = ndb_builder_init(b, buf, sizeof(buf));
    683 	assert(ok);
    684 	note = builder.note;
    685 
    686 	//memset(note->padding, 3, sizeof(note->padding));
    687 
    688 	ok = ndb_builder_set_content(b, hex_pk, strlen(hex_pk)); assert(ok);
    689 	ndb_builder_set_id(b, id); assert(ok);
    690 	ndb_builder_set_pubkey(b, pubkey); assert(ok);
    691 	ndb_builder_set_sig(b, sig); assert(ok);
    692 
    693 	ok = ndb_builder_new_tag(b); assert(ok);
    694 	ok = ndb_builder_push_tag_str(b, "p", 1); assert(ok);
    695 	ok = ndb_builder_push_tag_str(b, hex_pk, 64); assert(ok);
    696 
    697 	ok = ndb_builder_new_tag(b); assert(ok);
    698 	ok = ndb_builder_push_tag_str(b, "word", 4); assert(ok);
    699 	ok = ndb_builder_push_tag_str(b, "words", 5); assert(ok);
    700 	ok = ndb_builder_push_tag_str(b, "w", 1); assert(ok);
    701 
    702 	ok = ndb_builder_finalize(b, &note, NULL);
    703 	assert(ok);
    704 
    705 	// content should never be packed id
    706 	// TODO: figure out how to test this now that we don't expose it
    707 	// assert(note->content.packed.flag != NDB_PACKED_ID);
    708 	assert(ndb_tags_count(ndb_note_tags(note)) == 2);
    709 
    710 	// test iterator
    711 	struct ndb_iterator iter, *it = &iter;
    712 	
    713 	ndb_tags_iterate_start(note, it);
    714 	ok = ndb_tags_iterate_next(it);
    715 	assert(ok);
    716 
    717 	assert(ndb_tag_count(it->tag) == 2);
    718 	const char *p      = ndb_iter_tag_str(it, 0).str;
    719 	struct ndb_str hpk = ndb_iter_tag_str(it, 1);
    720 
    721 	hex_decode(hex_pk, 64, id, 32);
    722 
    723 	assert(hpk.flag == NDB_PACKED_ID);
    724 	assert(memcmp(hpk.id, id, 32) == 0);
    725 	assert(!strcmp(p, "p"));
    726 
    727 	ok = ndb_tags_iterate_next(it);
    728 	assert(ok);
    729 	assert(ndb_tag_count(it->tag) == 3);
    730 	assert(!strcmp(ndb_iter_tag_str(it, 0).str, "word"));
    731 	assert(!strcmp(ndb_iter_tag_str(it, 1).str, "words"));
    732 	assert(!strcmp(ndb_iter_tag_str(it, 2).str, "w"));
    733 
    734 	ok = ndb_tags_iterate_next(it);
    735 	assert(!ok);
    736 }
    737 
    738 static void test_empty_tags() {
    739 	struct ndb_builder builder, *b = &builder;
    740 	struct ndb_iterator iter, *it = &iter;
    741 	struct ndb_note *note;
    742 	int ok;
    743 	unsigned char buf[1024];
    744 
    745 	ok = ndb_builder_init(b, buf, sizeof(buf));
    746 	assert(ok);
    747 
    748 	ok = ndb_builder_finalize(b, &note, NULL);
    749 	assert(ok);
    750 
    751 	assert(ndb_tags_count(ndb_note_tags(note)) == 0);
    752 
    753 	ndb_tags_iterate_start(note, it);
    754 	ok = ndb_tags_iterate_next(it);
    755 	assert(!ok);
    756 }
    757 
    758 static void print_tag(struct ndb_note *note, struct ndb_tag *tag) {
    759 	struct ndb_str str;
    760 	int tag_count = ndb_tag_count(tag);
    761 	for (int i = 0; i < tag_count; i++) {
    762 		str = ndb_tag_str(note, tag, i);
    763 		if (str.flag == NDB_PACKED_ID) {
    764 			printf("<id> ");
    765 		} else {
    766 			printf("%s ", str.str);
    767 		}
    768 	}
    769 	printf("\n");
    770 }
    771 
    772 static void test_parse_contact_list()
    773 {
    774 	int size, written = 0;
    775 	unsigned char id[32];
    776 	static const int alloc_size = 2 << 18;
    777 	unsigned char *json = malloc(alloc_size);
    778 	unsigned char *buf = malloc(alloc_size);
    779 	struct ndb_note *note;
    780 
    781 	read_file("testdata/contacts.json", json, alloc_size, &written);
    782 
    783 	size = ndb_note_from_json((const char*)json, written, &note, buf, alloc_size);
    784 	assert(size > 0);
    785 	assert(size == 34328);
    786 
    787 	memcpy(id, ndb_note_id(note), 32);
    788 	memset(ndb_note_id(note), 0, 32);
    789 	assert(ndb_calculate_id(note, json, alloc_size, ndb_note_id(note)));
    790 	assert(!memcmp(ndb_note_id(note), id, 32));
    791 
    792 	const char* expected_content = 
    793 	"{\"wss://nos.lol\":{\"write\":true,\"read\":true},"
    794 	"\"wss://relay.damus.io\":{\"write\":true,\"read\":true},"
    795 	"\"ws://monad.jb55.com:8080\":{\"write\":true,\"read\":true},"
    796 	"\"wss://nostr.wine\":{\"write\":true,\"read\":true},"
    797 	"\"wss://welcome.nostr.wine\":{\"write\":true,\"read\":true},"
    798 	"\"wss://eden.nostr.land\":{\"write\":true,\"read\":true},"
    799 	"\"wss://relay.mostr.pub\":{\"write\":true,\"read\":true},"
    800 	"\"wss://nostr-pub.wellorder.net\":{\"write\":true,\"read\":true}}";
    801 
    802 	assert(!strcmp(expected_content, ndb_note_content(note)));
    803 	assert(ndb_note_created_at(note) == 1689904312);
    804 	assert(ndb_note_kind(note) == 3);
    805 	assert(ndb_tags_count(ndb_note_tags(note)) == 786);
    806 	//printf("note content length %d\n", ndb_note_content_length(note));
    807 	//printf("ndb_content_len %d, expected_len %ld\n", ndb_note_content_length(note), strlen(expected_content));
    808 	assert(ndb_note_content_length(note) == strlen(expected_content));
    809 
    810 	struct ndb_iterator iter, *it = &iter;
    811 	ndb_tags_iterate_start(note, it);
    812 
    813 	int tags = 0;
    814 	int total_elems = 0;
    815 
    816 	while (ndb_tags_iterate_next(it)) {
    817 		total_elems += ndb_tag_count(it->tag);
    818 		//printf("tag %d: ", tags);
    819 		if (tags == 0 || tags == 1 || tags == 2)
    820 			assert(ndb_tag_count(it->tag) == 3);
    821 
    822 		if (tags == 6)
    823 			assert(ndb_tag_count(it->tag) == 2);
    824 
    825 		if (tags == 7)
    826 			assert(!strcmp(ndb_tag_str(note, it->tag, 2).str, "wss://nostr-pub.wellorder.net"));
    827 
    828 		if (tags == 786) {
    829 			static unsigned char h[] = { 0x74, 0xfa, 0xe6, 0x66, 0x4c, 0x9e, 0x79, 0x98, 0x0c, 0x6a, 0xc1, 0x1c, 0x57, 0x75, 0xed, 0x30, 0x93, 0x2b, 0xe9, 0x26, 0xf5, 0xc4, 0x5b, 0xe8, 0xd6, 0x55, 0xe0, 0x0e, 0x35, 0xec, 0xa2, 0x88 };
    830 			assert(!memcmp(ndb_tag_str(note, it->tag, 1).id, h, 32));
    831 		}
    832 
    833 		//print_tag(it->note, it->tag);
    834 
    835 		tags += 1;
    836 	}
    837 
    838 	assert(tags == 786);
    839 	//printf("total_elems %d\n", total_elems);
    840 	assert(total_elems == 1580);
    841 
    842 	write_file("test_contacts_ndb_note", (unsigned char *)note, size);
    843 	//printf("wrote test_contacts_ndb_note (raw ndb_note)\n");
    844 
    845 	free(json);
    846 	free(buf);
    847 }
    848 
    849 static void test_replacement()
    850 {
    851 	static const int alloc_size = 1024 * 1024;
    852 	char *json = malloc(alloc_size);
    853 	unsigned char *buf = malloc(alloc_size);
    854 	struct ndb *ndb;
    855 	size_t len;
    856 	int written;
    857 	struct ndb_config config;
    858 	ndb_default_config(&config);
    859 
    860 	assert(ndb_init(&ndb, test_dir, &config));
    861 
    862 	read_file("testdata/old-new.json", (unsigned char*)json, alloc_size, &written);
    863 	assert(ndb_process_events(ndb, json, written));
    864 
    865 	ndb_destroy(ndb);
    866 	assert(ndb_init(&ndb, test_dir, &config));
    867 
    868 	struct ndb_txn txn;
    869 	assert(ndb_begin_query(ndb, &txn));
    870 
    871 	unsigned char pubkey[32] = { 0x1e, 0x48, 0x9f, 0x6a, 0x4f, 0xc5, 0xc7, 0xac, 0x47, 0x5e, 0xa9, 0x04, 0x17, 0x43, 0xb8, 0x53, 0x11, 0x73, 0x25, 0x92, 0x61, 0xec, 0x71, 0x54, 0x26, 0x41, 0x05, 0x1e, 0x22, 0xa3, 0x82, 0xac };
    872 
    873 	void *root = ndb_get_profile_by_pubkey(&txn, pubkey, &len, NULL);
    874 
    875 	assert(root);
    876 	int res = NdbProfileRecord_verify_as_root(root, len);
    877 	assert(res == 0);
    878 
    879 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(root);
    880 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
    881 	const char *name = NdbProfile_name_get(profile);
    882 
    883 	assert(!strcmp(name, "jb55"));
    884 
    885 	ndb_end_query(&txn);
    886 
    887 	free(json);
    888 	free(buf);
    889 }
    890 
    891 static void test_fetch_last_noteid()
    892 {
    893 	static const int alloc_size = 1024 * 1024;
    894 	char *json = malloc(alloc_size);
    895 	unsigned char *buf = malloc(alloc_size);
    896 	struct ndb *ndb;
    897 	size_t len;
    898 	int written;
    899 	struct ndb_config config;
    900 	ndb_default_config(&config);
    901 
    902 	assert(ndb_init(&ndb, test_dir, &config));
    903 
    904 	read_file("testdata/random.json", (unsigned char*)json, alloc_size, &written);
    905 	assert(ndb_process_events(ndb, json, written));
    906 
    907 	ndb_destroy(ndb);
    908 
    909 	assert(ndb_init(&ndb, test_dir, &config));
    910 
    911 	unsigned char id[32] = { 0xdc, 0x96, 0x4f, 0x4c, 0x89, 0x83, 0x64, 0x13, 0x8e, 0x81, 0x96, 0xf0, 0xc7, 0x33, 0x38, 0xc8, 0xcc, 0x3e, 0xbf, 0xa3, 0xaf, 0xdd, 0xbc, 0x7d, 0xd1, 0x58, 0xb4, 0x84, 0x7c, 0x1e, 0xbf, 0xa0 };
    912 
    913 	struct ndb_txn txn;
    914 	assert(ndb_begin_query(ndb, &txn));
    915 	struct ndb_note *note = ndb_get_note_by_id(&txn, id, &len, NULL);
    916 	assert(note != NULL);
    917 	assert(ndb_note_created_at(note) == 1650054135);
    918 	
    919 	unsigned char pk[32] = { 0x32, 0xe1, 0x82, 0x76, 0x35, 0x45, 0x0e, 0xbb, 0x3c, 0x5a, 0x7d, 0x12, 0xc1, 0xf8, 0xe7, 0xb2, 0xb5, 0x14, 0x43, 0x9a, 0xc1, 0x0a, 0x67, 0xee, 0xf3, 0xd9, 0xfd, 0x9c, 0x5c, 0x68, 0xe2, 0x45 };
    920 
    921 	unsigned char profile_note_id[32] = {
    922 		0xd1, 0x2c, 0x17, 0xbd, 0xe3, 0x09, 0x4a, 0xd3, 0x2f, 0x4a, 0xb8, 0x62, 0xa6, 0xcc, 0x6f, 0x5c, 0x28, 0x9c, 0xfe, 0x7d, 0x58, 0x02, 0x27, 0x0b, 0xdf, 0x34, 0x90, 0x4d, 0xf5, 0x85, 0xf3, 0x49
    923 	};
    924 
    925 	void *root = ndb_get_profile_by_pubkey(&txn, pk, &len, NULL);
    926 
    927 	assert(root);
    928 	int res = NdbProfileRecord_verify_as_root(root, len);
    929 	//printf("NdbProfileRecord verify result %d\n", res);
    930 	assert(res == 0);
    931 
    932 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(root);
    933 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
    934 	const char *lnurl = NdbProfileRecord_lnurl_get(profile_record);
    935 	const char *name = NdbProfile_name_get(profile);
    936 	uint64_t key = NdbProfileRecord_note_key_get(profile_record);
    937 	assert(name);
    938 	assert(lnurl);
    939 	assert(!strcmp(name, "jb55"));
    940 	assert(!strcmp(lnurl, "fixme"));
    941 
    942 	//printf("note_key %" PRIu64 "\n", key);
    943 
    944 	struct ndb_note *n = ndb_get_note_by_key(&txn, key, NULL);
    945 	ndb_end_query(&txn);
    946 	assert(memcmp(profile_note_id, ndb_note_id(n), 32) == 0);
    947 
    948 	//fwrite(profile, len, 1, stdout);
    949 
    950 	ndb_destroy(ndb);
    951 
    952 	free(json);
    953 	free(buf);
    954 }
    955 
    956 static void test_parse_contact_event()
    957 {
    958 	int written;
    959 	static const int alloc_size = 2 << 18;
    960 	char *json = malloc(alloc_size);
    961 	unsigned char *buf = malloc(alloc_size);
    962 	struct ndb_tce tce;
    963 
    964 	assert(read_file("testdata/contacts-event.json", (unsigned char*)json,
    965 			 alloc_size, &written));
    966 	assert(ndb_ws_event_from_json(json, written, &tce, buf, alloc_size, NULL));
    967 
    968 	assert(tce.evtype == NDB_TCE_EVENT);
    969 
    970 	free(json);
    971 	free(buf);
    972 }
    973 
    974 static void test_content_len()
    975 {
    976 	int written;
    977 	static const int alloc_size = 2 << 18;
    978 	char *json = malloc(alloc_size);
    979 	unsigned char *buf = malloc(alloc_size);
    980 	struct ndb_tce tce;
    981 
    982 	assert(read_file("testdata/failed_size.json", (unsigned char*)json,
    983 			 alloc_size, &written));
    984 	assert(ndb_ws_event_from_json(json, written, &tce, buf, alloc_size, NULL));
    985 
    986 	assert(tce.evtype == NDB_TCE_EVENT);
    987 	assert(ndb_note_content_length(tce.event.note) == 0);
    988 
    989 	free(json);
    990 	free(buf);
    991 }
    992 
    993 static void test_parse_filter_json()
    994 {
    995 	int i;
    996 	unsigned char buffer[1024];
    997 	unsigned char *pid;
    998 	const char *str;
    999 	uint64_t val;
   1000 	struct ndb_filter_elements *elems;
   1001 
   1002 	const unsigned char id_bytes[] = { 0x50, 0x04, 0xa0, 0x81, 0xe3, 0x97,
   1003 		0xc6, 0xda, 0x9d, 0xc2, 0xf2, 0xd6, 0xb3, 0x13, 0x40, 0x06,
   1004 		0xa9, 0xd0, 0xe8, 0xc1, 0xb4, 0x66, 0x89, 0xd9, 0xfe, 0x15,
   1005 		0x0b, 0xb2, 0xf2, 0x1a, 0x20, 0x4d };
   1006 
   1007 	const unsigned char id2_bytes[] = { 0xb1, 0x69, 0xf5, 0x96, 0x96, 0x89,
   1008 		0x17, 0xa1, 0xab, 0xeb, 0x42, 0x34, 0xd3, 0xcf, 0x3a, 0xa9,
   1009 		0xba, 0xee, 0x21, 0x12, 0xe5, 0x89, 0x98, 0xd1, 0x7c, 0x6d,
   1010 		0xb4, 0x16, 0xad, 0x33, 0xfe, 0x40 };
   1011 
   1012 #define HEX_ID "5004a081e397c6da9dc2f2d6b3134006a9d0e8c1b46689d9fe150bb2f21a204d"
   1013 #define HEX_PK "b169f596968917a1abeb4234d3cf3aa9baee2112e58998d17c6db416ad33fe40"
   1014 
   1015 	static const char *json = "{\"ids\": [\"" HEX_ID "\", \"" HEX_PK "\"], \"kinds\": [1,2,3], \"limit\": 10, \"#e\":[\"" HEX_PK "\"], \"#t\": [\"hashtag\"]}";
   1016 	struct ndb_filter filter, *f = &filter;
   1017 	ndb_filter_init(f);
   1018 
   1019 	assert(ndb_filter_from_json(json, strlen(json), f, buffer, sizeof(buffer)));
   1020 	assert(filter.finalized);
   1021 
   1022 	for (i = 0; i < filter.num_elements; i++) {
   1023 		elems = ndb_filter_get_elements(f, i);
   1024 
   1025 		switch (i) {
   1026 		case 0:
   1027 			assert(elems->field.type == NDB_FILTER_IDS);
   1028 			assert(elems->count == 2);
   1029 
   1030 			pid = ndb_filter_get_id_element(f, elems, 0);
   1031 			assert(!memcmp(pid, id_bytes, 32));
   1032 			pid = ndb_filter_get_id_element(f, elems, 1);
   1033 			assert(!memcmp(pid, id2_bytes, 32));
   1034 			break;
   1035 		case 1:
   1036 			assert(elems->field.type == NDB_FILTER_KINDS);
   1037 			assert(elems->count == 3);
   1038 			val = ndb_filter_get_int_element(elems, 0);
   1039 			assert(val == 1);
   1040 			val = ndb_filter_get_int_element(elems, 1);
   1041 			assert(val == 2);
   1042 			val = ndb_filter_get_int_element(elems, 2);
   1043 			assert(val == 3);
   1044 			break;
   1045 
   1046 		case 2:
   1047 			assert(elems->field.type == NDB_FILTER_LIMIT);
   1048 			val = ndb_filter_get_int_element(elems, 0);
   1049 			assert(val == 10);
   1050 			break;
   1051 
   1052 		case 3:
   1053 			assert(elems->field.type == NDB_FILTER_TAGS);
   1054 			assert(elems->field.tag == 'e');
   1055 			pid = ndb_filter_get_id_element(f, elems, 0);
   1056 			assert(pid != NULL);
   1057 			assert(!memcmp(pid, id2_bytes, 32));
   1058 			break;
   1059 
   1060 		case 4:
   1061 			assert(elems->field.type == NDB_FILTER_TAGS);
   1062 			assert(elems->field.tag == 't');
   1063 			str = ndb_filter_get_string_element(f, elems, 0);
   1064 			assert(!strcmp(str, "hashtag"));
   1065 			break;
   1066 		}
   1067 	}
   1068 
   1069 	ndb_filter_destroy(f);
   1070 }
   1071 
   1072 static void test_parse_json() {
   1073 	char hex_id[32] = {0};
   1074 	unsigned char buffer[1024];
   1075 	struct ndb_note *note;
   1076 #define HEX_ID "5004a081e397c6da9dc2f2d6b3134006a9d0e8c1b46689d9fe150bb2f21a204d"
   1077 #define HEX_PK "b169f596968917a1abeb4234d3cf3aa9baee2112e58998d17c6db416ad33fe40"
   1078 	static const char *json = 
   1079 		"{\"id\": \"" HEX_ID "\",\"pubkey\": \"" HEX_PK "\",\"created_at\": 1689836342,\"kind\": 1,\"tags\": [[\"p\",\"" HEX_ID "\"], [\"word\", \"words\", \"w\"]],\"content\": \"ε…±ι€šθͺž\",\"sig\": \"e4d528651311d567f461d7be916c37cbf2b4d530e672f29f15f353291ed6df60c665928e67d2f18861c5ca88\"}";
   1080 	int ok;
   1081 
   1082 	ok = ndb_note_from_json(json, strlen(json), &note, buffer, sizeof(buffer));
   1083 	assert(ok);
   1084 
   1085 	const char *content = ndb_note_content(note);
   1086 	unsigned char *id = ndb_note_id(note);
   1087 
   1088 	hex_decode(HEX_ID, 64, hex_id, sizeof(hex_id));
   1089 
   1090 	assert(!strcmp(content, "ε…±ι€šθͺž"));
   1091 	assert(!memcmp(id, hex_id, 32));
   1092 
   1093 	assert(ndb_tags_count(ndb_note_tags(note)) == 2);
   1094 
   1095 	struct ndb_iterator iter, *it = &iter;
   1096 	ndb_tags_iterate_start(note, it); assert(ok);
   1097 	ok = ndb_tags_iterate_next(it); assert(ok);
   1098 	assert(ndb_tag_count(it->tag) == 2);
   1099 	assert(!strcmp(ndb_iter_tag_str(it, 0).str, "p"));
   1100 	assert(!memcmp(ndb_iter_tag_str(it, 1).id, hex_id, 32));
   1101 
   1102 	ok = ndb_tags_iterate_next(it); assert(ok);
   1103 	assert(ndb_tag_count(it->tag) == 3);
   1104 	assert(!strcmp(ndb_iter_tag_str(it, 0).str, "word"));
   1105 	assert(!strcmp(ndb_iter_tag_str(it, 1).str, "words"));
   1106 	assert(!strcmp(ndb_iter_tag_str(it, 2).str, "w"));
   1107 }
   1108 
   1109 static void test_strings_work_before_finalization() {
   1110 	struct ndb_builder builder, *b = &builder;
   1111 	struct ndb_note *note;
   1112 	int ok;
   1113 	unsigned char buf[1024];
   1114 
   1115 	ok = ndb_builder_init(b, buf, sizeof(buf)); assert(ok);
   1116 	ndb_builder_set_content(b, "hello", 5);
   1117 
   1118 	assert(!strcmp(ndb_note_content(b->note), "hello"));
   1119 	assert(ndb_builder_finalize(b, &note, NULL));
   1120 
   1121 	assert(!strcmp(ndb_note_content(note), "hello"));
   1122 }
   1123 
   1124 static void test_parse_content() {
   1125 }
   1126 
   1127 static void test_parse_nevent() {
   1128 	unsigned char buf[4096];
   1129 	const char *content = "nostr:nevent1qqs9qhc0pjvp6jl2w6ppk5cft8ets8fhxy7fcqcjnp7g38whjy0x5aqpzpmhxue69uhkummnw3ezuamfdejsyg86np9a0kajstc8u9h846rmy6320wdepdeydfz8w8cv7kh9sqv02g947d58,#hashtag";
   1130 	struct ndb_blocks *blocks;
   1131 	struct ndb_block *block = NULL;
   1132 	struct nostr_bech32 *bech32;
   1133 	struct ndb_block_iterator iterator, *iter;
   1134 	iter = &iterator;
   1135 	int ok = 0;
   1136 
   1137 	static unsigned char event_id[] = { 0x50, 0x5f, 0x0f, 0x0c, 0x98, 0x1d, 0x4b, 0xea, 0x76, 0x82, 0x1b, 0x53,
   1138   0x09, 0x59, 0xf2, 0xb8, 0x1d, 0x37, 0x31, 0x3c, 0x9c, 0x03, 0x12, 0x98,
   1139   0x7c, 0x88, 0x9d, 0xd7, 0x91, 0x1e, 0x6a, 0x74 };
   1140 
   1141 	assert(ndb_parse_content(buf, sizeof(buf), content, strlen(content), &blocks));
   1142 	ndb_blocks_iterate_start(content, blocks, iter);
   1143 	assert(blocks->num_blocks == 3);
   1144 	while ((block = ndb_blocks_iterate_next(iter))) {
   1145 		switch (++ok) {
   1146 		case 1:
   1147 			assert(ndb_get_block_type(block) == BLOCK_MENTION_BECH32);
   1148 			bech32 = ndb_bech32_block(block);
   1149 			assert(bech32->type == NOSTR_BECH32_NEVENT);
   1150 			assert(!memcmp(bech32->nevent.event_id, event_id, 32));
   1151 			break;
   1152 		case 2:
   1153 			assert(ndb_get_block_type(block) == BLOCK_TEXT);
   1154 			assert(ndb_str_block_ptr(ndb_block_str(block))[0] == ',');
   1155 			break;
   1156 		case 3:
   1157 			assert(ndb_get_block_type(block) == BLOCK_HASHTAG);
   1158 			assert(!strncmp("hashtag", ndb_str_block_ptr(ndb_block_str(block)), 7));
   1159 			break;
   1160 		}
   1161 	}
   1162 	assert(ok == 3);
   1163 }
   1164 
   1165 static void test_bech32_parsing() {
   1166 	unsigned char buf[4096];
   1167 	const char *content = "https://damus.io/notedeck nostr:note1thp5828zk5xujrcuwdppcjnwlz43altca6269demenja3vqm5m2qclq35h";
   1168 
   1169 	struct ndb_blocks *blocks;
   1170 	struct ndb_block *block;
   1171 	struct ndb_str_block *str;
   1172 	struct ndb_block_iterator iterator, *iter;
   1173 
   1174 	iter = &iterator;
   1175 	assert(ndb_parse_content(buf, sizeof(buf), content, strlen(content), &blocks));
   1176 	assert(blocks->num_blocks == 3);
   1177 
   1178 	ndb_blocks_iterate_start(content, blocks, iter);
   1179 	int i = 0;
   1180 	while ((block = ndb_blocks_iterate_next(iter))) {
   1181 		str = ndb_block_str(block);
   1182 		switch (++i) {
   1183 		case 1:
   1184 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1185 			assert(!strncmp(str->str, "https://damus.io/notedeck", str->len));
   1186 			break;
   1187 		case 2:
   1188 			assert(ndb_get_block_type(block) == BLOCK_TEXT);
   1189 			assert(!strncmp(str->str, " ", str->len));
   1190 			break;
   1191 		case 3:
   1192 			assert(ndb_get_block_type(block) == BLOCK_MENTION_BECH32);
   1193 			assert(!strncmp(str->str, "note1thp5828zk5xujrcuwdppcjnwlz43altca6269demenja3vqm5m2qclq35h", str->len));
   1194 			break;
   1195 		}
   1196 	}
   1197 
   1198 	assert(i == 3);
   1199 }
   1200 
   1201 static void test_single_url_parsing() {
   1202 	unsigned char buf[4096];
   1203 	const char *content = "https://damus.io/notedeck";
   1204 
   1205 	struct ndb_blocks *blocks;
   1206 	struct ndb_block *block;
   1207 	struct ndb_str_block *str;
   1208 	struct ndb_block_iterator iterator, *iter;
   1209 
   1210 	iter = &iterator;
   1211 	assert(ndb_parse_content(buf, sizeof(buf), content, strlen(content), &blocks));
   1212 	assert(blocks->num_blocks == 1);
   1213 
   1214 	ndb_blocks_iterate_start(content, blocks, iter);
   1215 	int i = 0;
   1216 	while ((block = ndb_blocks_iterate_next(iter))) {
   1217 		str = ndb_block_str(block);
   1218 		switch (++i) {
   1219 		case 1:
   1220 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1221 			assert(!strncmp(str->str, "https://damus.io/notedeck", str->len));
   1222 			break;
   1223 		}
   1224 	}
   1225 
   1226 	assert(i == 1);
   1227 }
   1228 
   1229 static void test_comma_url_parsing() {
   1230 	unsigned char buf[4096];
   1231 	const char *content = "http://example.com,http://example.com";
   1232 
   1233 	struct ndb_blocks *blocks;
   1234 	struct ndb_block *block;
   1235 	struct ndb_str_block *str;
   1236 	struct ndb_block_iterator iterator, *iter;
   1237 
   1238 	iter = &iterator;
   1239 	assert(ndb_parse_content(buf, sizeof(buf), content, strlen(content), &blocks));
   1240 	assert(blocks->num_blocks == 3);
   1241 
   1242 	ndb_blocks_iterate_start(content, blocks, iter);
   1243 	int i = 0;
   1244 	while ((block = ndb_blocks_iterate_next(iter))) {
   1245 		str = ndb_block_str(block);
   1246 		switch (++i) {
   1247 		case 1:
   1248 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1249 			assert(!strncmp(str->str, "http://example.com", str->len));
   1250 			break;
   1251 		case 2:
   1252 			assert(ndb_get_block_type(block) == BLOCK_TEXT);
   1253 			assert(!strncmp(str->str, ",", str->len));
   1254 			break;
   1255 		case 3:
   1256 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1257 			assert(!strncmp(str->str, "http://example.com", str->len));
   1258 			break;
   1259 		}
   1260 	}
   1261 
   1262 	assert(i == 3);
   1263 }
   1264 
   1265 static void test_url_parsing() {
   1266 	unsigned char buf[4096];
   1267 #define DAMUSIO "https://github.com/damus-io"
   1268 #define JB55COM "https://jb55.com/"
   1269 #define WIKIORG "http://wikipedia.org"
   1270 	const char *content = DAMUSIO ", " JB55COM ", " WIKIORG;
   1271 	struct ndb_blocks *blocks;
   1272 	struct ndb_block *block;
   1273 	struct ndb_str_block *str;
   1274 	struct ndb_block_iterator iterator, *iter;
   1275 	iter = &iterator;
   1276 
   1277 	assert(ndb_parse_content(buf, sizeof(buf), content, strlen(content), &blocks));
   1278 	assert(blocks->num_blocks == 5);
   1279 
   1280 	ndb_blocks_iterate_start(content, blocks, iter);
   1281 	int i = 0;
   1282 	while ((block = ndb_blocks_iterate_next(iter))) {
   1283 		str = ndb_block_str(block);
   1284 		switch (++i) {
   1285 		case 1:
   1286 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1287 			assert(!strncmp(str->str, DAMUSIO, str->len));
   1288 			break;
   1289 		case 2:
   1290 			assert(ndb_get_block_type(block) == BLOCK_TEXT);
   1291 			assert(!strncmp(str->str, ", ", str->len));
   1292 			break;
   1293 		case 3:
   1294 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1295 			assert(!strncmp(str->str, JB55COM, str->len));
   1296 			break;
   1297 		case 4:
   1298 			assert(ndb_get_block_type(block) == BLOCK_TEXT);
   1299 			assert(!strncmp(str->str, ", ", str->len));
   1300 			break;
   1301 		case 5:
   1302 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1303 			assert(!strncmp(str->str, WIKIORG, str->len)); break;
   1304 		}
   1305 	}
   1306 
   1307 	assert(i == 5);
   1308 }
   1309 
   1310 
   1311 static void test_bech32_objects() {
   1312 	struct nostr_bech32 obj;
   1313 	unsigned char buf[4096];
   1314 	const char *nevent = "nevent1qqstjtqmd3lke9m3ftv49pagzxth4q2va4hy2m6kprl0p4y6es4vvnspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqsuamn8ghj7mr0vdskc6r0wd6qegay04";
   1315 
   1316 	unsigned char id[32] = {
   1317 	  0xb9, 0x2c, 0x1b, 0x6c, 0x7f, 0x6c, 0x97, 0x71, 0x4a, 0xd9, 0x52, 0x87,
   1318 	  0xa8, 0x11, 0x97, 0x7a, 0x81, 0x4c, 0xed, 0x6e, 0x45, 0x6f, 0x56, 0x08,
   1319 	  0xfe, 0xf0, 0xd4, 0x9a, 0xcc, 0x2a, 0xc6, 0x4e };
   1320 
   1321 	assert(parse_nostr_bech32(buf, sizeof(buf), nevent, strlen(nevent), &obj));
   1322 	assert(obj.type == NOSTR_BECH32_NEVENT);
   1323 	assert(!memcmp(obj.nevent.event_id, id, 32));
   1324 	assert(obj.nevent.relays.num_relays == 2);
   1325 	const char damus_relay[] = "wss://relay.damus.io";
   1326 	const char local_relay[] = "ws://localhost";
   1327 	assert(sizeof(damus_relay)-1 == obj.nevent.relays.relays[0].len);
   1328 	assert(!memcmp(obj.nevent.relays.relays[0].str, damus_relay, sizeof(damus_relay)-1));
   1329 	assert(!memcmp(obj.nevent.relays.relays[1].str, local_relay, sizeof(local_relay)-1));
   1330 }
   1331 
   1332 static void test_tce_eose() {
   1333 	unsigned char buf[1024];
   1334 	const char json[] = "[\"EOSE\",\"s\"]";
   1335 	struct ndb_tce tce;
   1336 	int ok;
   1337 
   1338 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
   1339 	assert(ok);
   1340 
   1341 	assert(tce.evtype == NDB_TCE_EOSE);
   1342 	assert(tce.subid_len == 1);
   1343 	assert(!memcmp(tce.subid, "s", 1));
   1344 }
   1345 
   1346 static void test_tce_command_result() {
   1347 	unsigned char buf[1024];
   1348 	const char json[] = "[\"OK\",\"\",true,\"blocked: ok\"]";
   1349 	struct ndb_tce tce;
   1350 	int ok;
   1351 
   1352 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
   1353 	assert(ok);
   1354 
   1355 	assert(tce.evtype == NDB_TCE_OK);
   1356 	assert(tce.subid_len == 0);
   1357 	assert(tce.command_result.ok == 1);
   1358 	assert(!memcmp(tce.subid, "", 0));
   1359 }
   1360 
   1361 static void test_tce_command_result_empty_msg() {
   1362 	unsigned char buf[1024];
   1363 	const char json[] = "[\"OK\",\"b1d8f68d39c07ce5c5ea10c235100d529b2ed2250140b36a35d940b712dc6eff\",true,\"\"]";
   1364 	struct ndb_tce tce;
   1365 	int ok;
   1366 
   1367 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
   1368 	assert(ok);
   1369 
   1370 	assert(tce.evtype == NDB_TCE_OK);
   1371 	assert(tce.subid_len == 64);
   1372 	assert(tce.command_result.ok == 1);
   1373 	assert(tce.command_result.msglen == 0);
   1374 	assert(!memcmp(tce.subid, "b1d8f68d39c07ce5c5ea10c235100d529b2ed2250140b36a35d940b712dc6eff", 0));
   1375 }
   1376 
   1377 // test to-client event
   1378 static void test_tce() {
   1379 
   1380 #define HEX_ID "5004a081e397c6da9dc2f2d6b3134006a9d0e8c1b46689d9fe150bb2f21a204d"
   1381 #define HEX_PK "b169f596968917a1abeb4234d3cf3aa9baee2112e58998d17c6db416ad33fe40"
   1382 #define JSON "{\"id\": \"" HEX_ID "\",\"pubkey\": \"" HEX_PK "\",\"created_at\": 1689836342,\"kind\": 1,\"tags\": [[\"p\",\"" HEX_ID "\"], [\"word\", \"words\", \"w\"]],\"content\": \"ε…±ι€šθͺž\",\"sig\": \"e4d528651311d567f461d7be916c37cbf2b4d530e672f29f15f353291ed6df60c665928e67d2f18861c5ca88\"}"
   1383 	unsigned char buf[1024];
   1384 	const char json[] = "[\"EVENT\",\"subid123\"," JSON "]";
   1385 	struct ndb_tce tce;
   1386 	int ok;
   1387 
   1388 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
   1389 	assert(ok);
   1390 
   1391 	assert(tce.evtype == NDB_TCE_EVENT);
   1392 	assert(tce.subid_len == 8);
   1393 	assert(!memcmp(tce.subid, "subid123", 8));
   1394 
   1395 #undef HEX_ID
   1396 #undef HEX_PK
   1397 #undef JSON
   1398 }
   1399 
   1400 #define TEST_BUF_SIZE 10  // For simplicity
   1401 
   1402 static void test_queue_init_pop_push() {
   1403 	struct prot_queue q;
   1404 	int buffer[TEST_BUF_SIZE];
   1405 	int data;
   1406 
   1407 	// Initialize
   1408 	assert(prot_queue_init(&q, buffer, sizeof(buffer), sizeof(int)) == 1);
   1409 
   1410 	// Push and Pop
   1411 	data = 5;
   1412 	assert(prot_queue_push(&q, &data) == 1);
   1413 	prot_queue_pop(&q, &data);
   1414 	assert(data == 5);
   1415 
   1416 	// Push to full, and then fail to push
   1417 	for (int i = 0; i < TEST_BUF_SIZE; i++) {
   1418 		assert(prot_queue_push(&q, &i) == 1);
   1419 	}
   1420 	assert(prot_queue_push(&q, &data) == 0);  // Should fail as queue is full
   1421 
   1422 	// Pop to empty, and then fail to pop
   1423 	for (int i = 0; i < TEST_BUF_SIZE; i++) {
   1424 		assert(prot_queue_try_pop_all(&q, &data, 1) == 1);
   1425 		assert(data == i);
   1426 	}
   1427 	assert(prot_queue_try_pop_all(&q, &data, 1) == 0);  // Should fail as queue is empty
   1428 }
   1429 
   1430 // This function will be used by threads to test thread safety.
   1431 void* thread_func(void* arg) {
   1432 	struct prot_queue* q = (struct prot_queue*) arg;
   1433 	int data;
   1434 
   1435 	for (int i = 0; i < 100; i++) {
   1436 		data = i;
   1437 		prot_queue_push(q, &data);
   1438 		prot_queue_pop(q, &data);
   1439 	}
   1440 	return NULL;
   1441 }
   1442 
   1443 static void test_queue_thread_safety() {
   1444 	struct prot_queue q;
   1445 	int buffer[TEST_BUF_SIZE];
   1446 	pthread_t threads[2];
   1447 
   1448 	assert(prot_queue_init(&q, buffer, sizeof(buffer), sizeof(int)) == 1);
   1449 
   1450 	// Create threads
   1451 	for (int i = 0; i < 2; i++) {
   1452 		pthread_create(&threads[i], NULL, thread_func, &q);
   1453 	}
   1454 
   1455 	// Join threads
   1456 	for (int i = 0; i < 2; i++) {
   1457 		pthread_join(threads[i], NULL);
   1458 	}
   1459 
   1460 	// After all operations, the queue should be empty
   1461 	int data;
   1462 	assert(prot_queue_try_pop_all(&q, &data, 1) == 0);
   1463 }
   1464 
   1465 static void test_queue_boundary_conditions() {
   1466     struct prot_queue q;
   1467     int buffer[TEST_BUF_SIZE];
   1468     int data;
   1469 
   1470     // Initialize
   1471     assert(prot_queue_init(&q, buffer, sizeof(buffer), sizeof(int)) == 1);
   1472 
   1473     // Push to full
   1474     for (int i = 0; i < TEST_BUF_SIZE; i++) {
   1475         assert(prot_queue_push(&q, &i) == 1);
   1476     }
   1477 
   1478     // Try to push to a full queue
   1479     int old_head = q.head;
   1480     int old_tail = q.tail;
   1481     int old_count = q.count;
   1482     assert(prot_queue_push(&q, &data) == 0);
   1483     
   1484     // Assert the queue's state has not changed
   1485     assert(old_head == q.head);
   1486     assert(old_tail == q.tail);
   1487     assert(old_count == q.count);
   1488 
   1489     // Pop to empty
   1490     for (int i = 0; i < TEST_BUF_SIZE; i++) {
   1491         assert(prot_queue_try_pop_all(&q, &data, 1) == 1);
   1492     }
   1493 
   1494     // Try to pop from an empty queue
   1495     old_head = q.head;
   1496     old_tail = q.tail;
   1497     old_count = q.count;
   1498     assert(prot_queue_try_pop_all(&q, &data, 1) == 0);
   1499     
   1500     // Assert the queue's state has not changed
   1501     assert(old_head == q.head);
   1502     assert(old_tail == q.tail);
   1503     assert(old_count == q.count);
   1504 }
   1505 
   1506 static void test_fast_strchr()
   1507 {
   1508 	// Test 1: Basic test
   1509 	const char *testStr1 = "Hello, World!";
   1510 	assert(fast_strchr(testStr1, 'W', strlen(testStr1)) == testStr1 + 7);
   1511 
   1512 	// Test 2: Character not present in the string
   1513 	assert(fast_strchr(testStr1, 'X', strlen(testStr1)) == NULL);
   1514 
   1515 	// Test 3: Multiple occurrences of the character
   1516 	const char *testStr2 = "Multiple occurrences.";
   1517 	assert(fast_strchr(testStr2, 'u', strlen(testStr2)) == testStr2 + 1);
   1518 
   1519 	// Test 4: Check with an empty string
   1520 	const char *testStr3 = "";
   1521 	assert(fast_strchr(testStr3, 'a', strlen(testStr3)) == NULL);
   1522 
   1523 	// Test 5: Check with a one-character string
   1524 	const char *testStr4 = "a";
   1525 	assert(fast_strchr(testStr4, 'a', strlen(testStr4)) == testStr4);
   1526 
   1527 	// Test 6: Check the last character in the string
   1528 	const char *testStr5 = "Last character check";
   1529 	assert(fast_strchr(testStr5, 'k', strlen(testStr5)) == testStr5 + 19);
   1530 
   1531 	// Test 7: Large string test (>16 bytes)
   1532 	char *testStr6 = "This is a test for large strings with more than 16 bytes.";
   1533 	assert(fast_strchr(testStr6, 'm', strlen(testStr6)) == testStr6 + 38);
   1534 }
   1535 
   1536 static void test_tag_query()
   1537 {
   1538 	struct ndb *ndb;
   1539 	struct ndb_txn txn;
   1540 	struct ndb_filter filters[1], *f = &filters[0];
   1541 	struct ndb_config config;
   1542 	struct ndb_query_result results[4];
   1543 	int count, cap;
   1544 	uint64_t subid, note_ids[1];
   1545 	ndb_default_config(&config);
   1546 
   1547 	cap = sizeof(results) / sizeof(results[0]);
   1548 
   1549 	assert(ndb_init(&ndb, test_dir, &config));
   1550 
   1551 	const char *ev = "[\"EVENT\",\"s\",{\"id\": \"7fd6e4286e595b60448bf69d8ec4a472c5ad14521555813cdfce1740f012aefd\",\"pubkey\": \"b85beab689aed6a10110cc3cdd6e00ac37a2f747c4e60b18a31f4352a5bfb6ed\",\"created_at\": 1704762185,\"kind\": 1,\"tags\": [[\"t\",\"hashtag\"]],\"content\": \"hi\",\"sig\": \"5b05669af5a322730731b13d38667464ea3b45bef1861e26c99ef1815d7e8d557a76e06afa5fffa1dcd207402b92ae7dda6ef411ea515df2bca58d74e6f2772e\"}]";
   1552 
   1553 	f = &filters[0];
   1554 	ndb_filter_init(f);
   1555 	ndb_filter_start_tag_field(f, 't');
   1556 	ndb_filter_add_str_element(f, "hashtag");
   1557 	ndb_filter_end_field(f);
   1558 	ndb_filter_end(f);
   1559 
   1560 	assert((subid = ndb_subscribe(ndb, f, 1)));
   1561 	assert(ndb_process_event(ndb, ev, strlen(ev)));
   1562 	ndb_wait_for_notes(ndb, subid, note_ids, 1);
   1563 
   1564 	ndb_begin_query(ndb, &txn);
   1565 
   1566 	assert(ndb_query(&txn, f, 1, results, cap, &count));
   1567 	assert(count == 1);
   1568 	assert(!strcmp(ndb_note_content(results[0].note), "hi"));
   1569 
   1570 	ndb_end_query(&txn);
   1571 	ndb_filter_destroy(f);
   1572 	ndb_destroy(ndb);
   1573 }
   1574 
   1575 static void test_query()
   1576 {
   1577 	struct ndb *ndb;
   1578 	struct ndb_txn txn;
   1579 	struct ndb_filter filters[2], *f;
   1580 	struct ndb_config config;
   1581 	struct ndb_query_result results[4];
   1582 	int count, cap, nres;
   1583 	uint64_t subid, note_ids[4];
   1584 	ndb_default_config(&config);
   1585 
   1586 	cap = sizeof(results) / sizeof(results[0]);
   1587 
   1588 	const unsigned char id[] = {
   1589 	  0x03, 0x36, 0x94, 0x8b, 0xdf, 0xbf, 0x5f, 0x93, 0x98, 0x02, 0xeb, 0xa0,
   1590 	  0x3a, 0xa7, 0x87, 0x35, 0xc8, 0x28, 0x25, 0x21, 0x1e, 0xec, 0xe9, 0x87,
   1591 	  0xa6, 0xd2, 0xe2, 0x0e, 0x3c, 0xff, 0xf9, 0x30
   1592 	};
   1593 
   1594 	const unsigned char id2[] = {
   1595 	  0x0a, 0x35, 0x0c, 0x58, 0x51, 0xaf, 0x6f, 0x6c, 0xe3, 0x68, 0xba, 0xb4,
   1596 	  0xe2, 0xd4, 0xfe, 0x44, 0x2a, 0x13, 0x18, 0x64, 0x2c, 0x7f, 0xe5, 0x8d,
   1597 	  0xe5, 0x39, 0x21, 0x03, 0x70, 0x0c, 0x10, 0xfc
   1598 	};
   1599 
   1600 	const char *ev = "[\"EVENT\",\"s\",{\"id\": \"0336948bdfbf5f939802eba03aa78735c82825211eece987a6d2e20e3cfff930\",\"pubkey\": \"aeadd3bf2fd92e509e137c9e8bdf20e99f286b90be7692434e03c015e1d3bbfe\",\"created_at\": 1704401597,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"232395427153b693e0426b93d89a8319324d8657e67d23953f014a22159d2127b4da20b95644b3e34debd5e20be0401c283e7308ccb63c1c1e0f81cac7502f09\"}]";
   1601 	const char *ev2 = "[\"EVENT\",\"s\",{\"id\": \"0a350c5851af6f6ce368bab4e2d4fe442a1318642c7fe58de5392103700c10fc\",\"pubkey\": \"dfa3fc062f7430dab3d947417fd3c6fb38a7e60f82ffe3387e2679d4c6919b1d\",\"created_at\": 1704404822,\"kind\": 1,\"tags\": [],\"content\": \"hello2\",\"sig\": \"48a0bb9560b89ee2c6b88edcf1cbeeff04f5e1b10d26da8564cac851065f30fa6961ee51f450cefe5e8f4895e301e8ffb2be06a2ff44259684fbd4ea1c885696\"}]";
   1602 	const char *ev3 = "[\"EVENT\",\"s\",{\"id\": \"20d2b66e1a3ac4a2afe22866ad742091b6267e6e614303de062adb33e12c9931\",\"pubkey\": \"7987bfb2632d561088fc8e3c30a95836f822e4f53633228ec92ae2f5cd6690aa\",\"created_at\": 1704408561,\"kind\": 2,\"tags\": [],\"content\": \"what\",\"sig\": \"cc8533bf177ac87771a5218a04bed24f7a1706f0b2d92700045cdeb38accc5507c6c8de09525e43190df3652012b554d4efe7b82ab268a87ff6f23da44e16a8f\"}]";
   1603 	const char *ev4 = "[\"EVENT\",\"s\",{\"id\": \"8a2057c13c1c57b536eab78e6c55428732d33b6b5b234c1f5eab2b5918c37fa1\",\"pubkey\": \"303b5851504da5caa14142e9e2e1b1b60783c48d6f137c205019d46d09244c26\",\"created_at\": 1704408730,\"kind\": 2,\"tags\": [],\"content\": \"hmm\",\"sig\": \"e7cd3029042d41964192411929cade59592840af766da6420077ccc57a61405312db6ca879150db01f53c3b81c477cec5d6bd49f9dc10937267cacf7e5c784b3\"}]";
   1604 
   1605 	assert(ndb_init(&ndb, test_dir, &config));
   1606 
   1607 	f = &filters[0];
   1608 	ndb_filter_init(f);
   1609 	ndb_filter_start_field(f, NDB_FILTER_IDS);
   1610 	ndb_filter_add_id_element(f, id2);
   1611 	ndb_filter_add_id_element(f, id);
   1612 	assert(ndb_filter_current_element(f)->count == 2);
   1613 	ndb_filter_end_field(f);
   1614 	ndb_filter_end(f);
   1615 
   1616 	assert((subid = ndb_subscribe(ndb, f, 1)));
   1617 
   1618 	assert(ndb_process_event(ndb, ev, strlen(ev)));
   1619 	assert(ndb_process_event(ndb, ev2, strlen(ev2)));
   1620 
   1621 	for (nres = 2; nres > 0;)
   1622 		nres -= ndb_wait_for_notes(ndb, subid, note_ids, 2);
   1623 
   1624 	ndb_begin_query(ndb, &txn);
   1625 	assert(ndb_query(&txn, f, 1, results, cap, &count));
   1626 	assert(count == 2);
   1627 	assert(0 == memcmp(ndb_note_id(results[0].note), id2, 32));
   1628 	ndb_end_query(&txn);
   1629 
   1630 	ndb_filter_destroy(f);
   1631 	ndb_filter_init(f);
   1632 	ndb_filter_start_field(f, NDB_FILTER_KINDS);
   1633 	ndb_filter_add_int_element(f, 2);
   1634 	ndb_filter_end_field(f);
   1635 	ndb_filter_start_field(f, NDB_FILTER_LIMIT);
   1636 	ndb_filter_add_int_element(f, 2);
   1637 	ndb_filter_end_field(f);
   1638 	ndb_filter_end(f);
   1639 
   1640 	assert((subid = ndb_subscribe(ndb, f, 1)));
   1641 	assert(ndb_process_event(ndb, ev3, strlen(ev3)));
   1642 	assert(ndb_process_event(ndb, ev4, strlen(ev4)));
   1643 
   1644 	for (nres = 2; nres > 0;)
   1645 		nres -= ndb_wait_for_notes(ndb, subid, note_ids, 2);
   1646 	ndb_begin_query(ndb, &txn);
   1647 
   1648 	count = 0;
   1649 	assert(ndb_query(&txn, f, 1, results, cap, &count));
   1650 	//ndb_print_kind_keys(&txn);
   1651 	assert(count == 2);
   1652 	assert(!strcmp(ndb_note_content(results[0].note), "hmm"));
   1653 	assert(!strcmp(ndb_note_content(results[1].note), "what"));
   1654 
   1655 	ndb_end_query(&txn);
   1656 	ndb_filter_destroy(f);
   1657 	ndb_destroy(ndb);
   1658 }
   1659 
   1660 static void test_fulltext()
   1661 {
   1662 	struct ndb *ndb;
   1663 	struct ndb_txn txn;
   1664 	int written;
   1665 	static const int alloc_size = 2 << 18;
   1666 	char *json = malloc(alloc_size);
   1667 	struct ndb_text_search_results results;
   1668 	struct ndb_config config;
   1669 	struct ndb_text_search_config search_config;
   1670 	ndb_default_config(&config);
   1671 	ndb_default_text_search_config(&search_config);
   1672 
   1673 	assert(ndb_init(&ndb, test_dir, &config));
   1674 
   1675 	read_file("testdata/search.json", (unsigned char*)json, alloc_size, &written);
   1676 	assert(ndb_process_client_events(ndb, json, written));
   1677 	ndb_destroy(ndb);
   1678 	assert(ndb_init(&ndb, test_dir, &config));
   1679 
   1680 	ndb_begin_query(ndb, &txn);
   1681 	ndb_text_search(&txn, "Jump Over", &results, &search_config);
   1682 	ndb_end_query(&txn);
   1683 
   1684 	ndb_destroy(ndb);
   1685 
   1686 	free(json);
   1687 
   1688 }
   1689 
   1690 static void test_varint(uint64_t value) {
   1691 	unsigned char buffer[10];
   1692 	struct cursor cursor;
   1693 	uint64_t result;
   1694 
   1695 	// Initialize cursor
   1696 	cursor.start = buffer;
   1697 	cursor.p = buffer;
   1698 	cursor.end = buffer + sizeof(buffer);
   1699 
   1700 	// Push the value
   1701 	assert(cursor_push_varint(&cursor, value));
   1702 
   1703 	// Reset cursor for reading
   1704 	cursor.p = buffer;
   1705 
   1706 	// Pull the value
   1707 	if (!cursor_pull_varint(&cursor, &result)) {
   1708 		printf("Test failed for value %" PRIu64 " \n", value);
   1709 		assert(!"Failed to pull value");
   1710 	}
   1711 
   1712 	// Check if the pushed and pulled values are the same
   1713 	if (value != result) {
   1714 		printf("Test failed for value %" PRIu64 "\n", value);
   1715 		assert(!"test failed");
   1716 	}
   1717 }
   1718 
   1719 static int test_varints() {
   1720 	test_varint(0);
   1721 	test_varint(127); // Edge case for 1-byte varint
   1722 	test_varint(128); // Edge case for 2-byte varint
   1723 	test_varint(16383); // Edge case for 2-byte varint
   1724 	test_varint(16384); // Edge case for 3-byte varint
   1725 	test_varint(2097151); // Edge case for 3-byte varint
   1726 	test_varint(2097152); // Edge case for 4-byte varint
   1727 	test_varint(268435455); // Edge case for 4-byte varint
   1728 	test_varint(268435456); // Edge case for 5-byte varint
   1729 	test_varint(34359738367ULL); // Edge case for 5-byte varint
   1730 	test_varint(34359738368ULL); // Edge case for 6-byte varint
   1731 	test_varint(4398046511103ULL); // Edge case for 6-byte varint
   1732 	test_varint(4398046511104ULL); // Edge case for 7-byte varint
   1733 	test_varint(562949953421311ULL); // Edge case for 7-byte varint
   1734 	test_varint(562949953421312ULL); // Edge case for 8-byte varint
   1735 	test_varint(72057594037927935ULL); // Edge case for 8-byte varint
   1736 	test_varint(72057594037927936ULL); // Edge case for 9-byte varint
   1737 	test_varint(9223372036854775807ULL); // Maximum 64-bit integer
   1738 
   1739 	return 0;
   1740 }
   1741 
   1742 static void test_subscriptions()
   1743 {
   1744 	struct ndb *ndb;
   1745 	struct ndb_config config;
   1746 	struct ndb_filter filter, *f = &filter;
   1747 	uint64_t subid;
   1748 	uint64_t note_id = 0;
   1749 	struct ndb_txn txn;
   1750 	struct ndb_note *note;
   1751 	ndb_default_config(&config);
   1752 
   1753 	const char *ev = "[\"EVENT\",\"s\",{\"id\": \"3718b368de4d01a021990e6e00dce4bdf860caed21baffd11b214ac498e7562e\",\"pubkey\": \"57c811c86a871081f52ca80e657004fe0376624a978f150073881b6daf0cbf1d\",\"created_at\": 1704300579,\"kind\": 1337,\"tags\": [],\"content\": \"test\",\"sig\": \"061c36d4004d8342495eb22e8e7c2e2b6e1a1c7b4ae6077fef09f9a5322c561b88bada4f63ff05c9508cb29d03f50f71ef3c93c0201dbec440fc32eda87f273b\"}]";
   1754 
   1755 	assert(ndb_init(&ndb, test_dir, &config));
   1756 
   1757 	assert(ndb_filter_init(f));
   1758 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
   1759 	assert(ndb_filter_add_int_element(f, 1337));
   1760 	ndb_filter_end_field(f);
   1761 	ndb_filter_end(f);
   1762 
   1763 	assert((subid = ndb_subscribe(ndb, f, 1)));
   1764 
   1765 	assert(ndb_process_event(ndb, ev, strlen(ev)));
   1766 
   1767 	assert(ndb_wait_for_notes(ndb, subid, &note_id, 1) == 1);
   1768 	assert(note_id > 0);
   1769 	assert(ndb_begin_query(ndb, &txn));
   1770 
   1771 	assert((note = ndb_get_note_by_key(&txn, note_id, NULL)));
   1772 	assert(!strcmp(ndb_note_content(note), "test"));
   1773 
   1774 	// unsubscribe
   1775 	assert(ndb_num_subscriptions(ndb) == 1);
   1776 	assert(ndb_unsubscribe(ndb, subid));
   1777 	assert(ndb_num_subscriptions(ndb) == 0);
   1778 
   1779 	ndb_end_query(&txn);
   1780 	ndb_filter_destroy(f);
   1781 	ndb_destroy(ndb);
   1782 }
   1783 
   1784 static void test_weird_note_corruption() {
   1785 	struct ndb *ndb;
   1786 	struct ndb_config config;
   1787 	struct ndb_blocks *blocks;
   1788 	struct ndb_block *block;
   1789 	struct ndb_str_block *str;
   1790 	struct ndb_block_iterator iterator, *iter = &iterator;
   1791 	struct ndb_filter filter, *f = &filter;
   1792 	uint64_t subid;
   1793 	uint64_t note_id = 0;
   1794 	struct ndb_txn txn;
   1795 	struct ndb_note *note;
   1796 	ndb_default_config(&config);
   1797 
   1798 	const char *ev = "[\"EVENT\",\"a\",{\"content\":\"https://damus.io/notedeck\",\"created_at\":1722537589,\"id\":\"1876ca8cd29afba5805e698cf04ac6611d50e5e5a22e1efb895816a4c5790a1b\",\"kind\":1,\"pubkey\":\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"sig\":\"2478aac6e9e66e5a04938d54544928589b55d2a324a7229ef7e709903b5dd12dc9a279abf81ed5753692cd30f62213fd3e0adf8b835543616a60e2d7010f0627\",\"tags\":[[\"e\",\"423fdf3f6e438fded84fe496643008eada5c1db7ba80428521c2c098f1173b83\",\"\",\"root\"],[\"p\",\"406a61077fe67f0eda4be931572c522f937952ddb024c87673e3de6b37e9a98f\"]]}]";
   1799 	assert(ndb_init(&ndb, test_dir, &config));
   1800 
   1801 	assert(ndb_filter_init(f));
   1802 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
   1803 	assert(ndb_filter_add_int_element(f, 1));
   1804 	ndb_filter_end_field(f);
   1805 	ndb_filter_end(f);
   1806 
   1807 	assert((subid = ndb_subscribe(ndb, f, 1)));
   1808 	assert(ndb_process_event(ndb, ev, strlen(ev)));
   1809 
   1810 	assert(ndb_wait_for_notes(ndb, subid, &note_id, 1) == 1);
   1811 	assert(note_id > 0);
   1812 	assert(ndb_begin_query(ndb, &txn));
   1813 
   1814 	assert((note = ndb_get_note_by_key(&txn, note_id, NULL)));
   1815 	assert(!strcmp(ndb_note_content(note), "https://damus.io/notedeck"));
   1816 	assert(ndb_note_content_length(note) == 25);
   1817 
   1818 	assert(ndb_num_subscriptions(ndb) == 1);
   1819 	assert(ndb_unsubscribe(ndb, subid));
   1820 	assert(ndb_num_subscriptions(ndb) == 0);
   1821 
   1822 	blocks = ndb_get_blocks_by_key(ndb, &txn, note_id);
   1823 
   1824 	ndb_blocks_iterate_start(ndb_note_content(note), blocks, iter);
   1825 	//printf("url num blocks: %d\n", blocks->num_blocks);
   1826 	assert(blocks->num_blocks == 1);
   1827 	int i = 0;
   1828 	while ((block = ndb_blocks_iterate_next(iter))) {
   1829 		str = ndb_block_str(block);
   1830 		//printf("block (%d): %d:'%.*s'\n", ndb_get_block_type(block), str->len, str->len, str->str);
   1831 		switch (++i) {
   1832 		case 1:
   1833 			assert(ndb_get_block_type(block) == BLOCK_URL);
   1834 			assert(str->len == 25);
   1835 			assert(!strncmp(str->str, "https://damus.io/notedeck", str->len));
   1836 			break;
   1837 		}
   1838 	}
   1839 	assert(i == 1);
   1840 
   1841 	ndb_end_query(&txn);
   1842 	ndb_filter_destroy(f);
   1843 	ndb_destroy(ndb);
   1844 }
   1845 
   1846 static void test_filter_eq() {
   1847 	struct ndb_filter filter, *f = &filter;
   1848 	struct ndb_filter filter2, *f2 = &filter2;
   1849 
   1850 	ndb_filter_init(f);
   1851 	assert(ndb_filter_start_field(f, NDB_FILTER_UNTIL));
   1852 	assert(ndb_filter_add_int_element(f, 42));
   1853 	ndb_filter_end_field(f);
   1854 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
   1855 	assert(ndb_filter_add_int_element(f, 1));
   1856 	assert(ndb_filter_add_int_element(f, 2));
   1857 	ndb_filter_end_field(f);
   1858 	ndb_filter_end(f);
   1859 
   1860 	ndb_filter_init(f2);
   1861 	assert(ndb_filter_start_field(f2, NDB_FILTER_KINDS));
   1862 	assert(ndb_filter_add_int_element(f2, 2));
   1863 	assert(ndb_filter_add_int_element(f2, 1));
   1864 	ndb_filter_end_field(f2);
   1865 	assert(ndb_filter_start_field(f2, NDB_FILTER_UNTIL));
   1866 	assert(ndb_filter_add_int_element(f2, 42));
   1867 	ndb_filter_end_field(f2);
   1868 	ndb_filter_end(f2);
   1869 
   1870 	assert(ndb_filter_eq(f, f2));
   1871 
   1872 	ndb_filter_destroy(f);
   1873 	ndb_filter_destroy(f2);
   1874 }
   1875 
   1876 static void test_filter_is_subset() {
   1877 	struct ndb_filter global, *g = &global;
   1878 	struct ndb_filter kind, *k = &kind;
   1879 	struct ndb_filter kind_and_id, *ki = &kind_and_id;
   1880 
   1881 	const unsigned char id[] = {
   1882 	  0x03, 0x36, 0x94, 0x8b, 0xdf, 0xbf, 0x5f, 0x93, 0x98, 0x02, 0xeb, 0xa0,
   1883 	  0x3a, 0xa7, 0x87, 0x35, 0xc8, 0x28, 0x25, 0x21, 0x1e, 0xec, 0xe9, 0x87,
   1884 	  0xa6, 0xd2, 0xe2, 0x0e, 0x3c, 0xff, 0xf9, 0x30
   1885 	};
   1886 
   1887 	ndb_filter_init(g);
   1888 	ndb_filter_end(g);
   1889 
   1890 	ndb_filter_init(k);
   1891 	assert(ndb_filter_start_field(k, NDB_FILTER_KINDS));
   1892 	assert(ndb_filter_add_int_element(k, 1));
   1893 	ndb_filter_end_field(k);
   1894 	ndb_filter_end(k);
   1895 
   1896 	ndb_filter_init(ki);
   1897 	assert(ndb_filter_start_field(ki, NDB_FILTER_KINDS));
   1898 	assert(ndb_filter_add_int_element(ki, 1));
   1899 	ndb_filter_end_field(ki);
   1900 	assert(ndb_filter_start_field(ki, NDB_FILTER_IDS));
   1901 	assert(ndb_filter_add_id_element(ki, id));
   1902 	ndb_filter_end_field(ki);
   1903 	ndb_filter_end(ki);
   1904 
   1905 	assert(ndb_filter_is_subset_of(g, k) == 0);
   1906 	assert(ndb_filter_is_subset_of(k, g) == 1);
   1907 	assert(ndb_filter_is_subset_of(ki, k) == 1);
   1908 	assert(ndb_filter_is_subset_of(k, ki) == 0);
   1909 
   1910 	ndb_filter_destroy(k);
   1911 	ndb_filter_destroy(ki);
   1912 }
   1913 
   1914 static void test_filter_search()
   1915 {
   1916 	struct ndb_filter filter, *f = &filter;
   1917 
   1918 	assert(ndb_filter_init_with(f, 1));
   1919 
   1920 	assert(ndb_filter_start_field(f, NDB_FILTER_SEARCH));
   1921 	assert(ndb_filter_add_str_element(f, "searchterm"));
   1922 	assert(!ndb_filter_add_str_element(f, "searchterm 2"));
   1923 	ndb_filter_end_field(f);
   1924 
   1925 	assert(ndb_filter_end(f));
   1926 	ndb_filter_destroy(f);
   1927 }
   1928 
   1929 static void test_filter_parse_search_json() {
   1930 	const char *json = "{\"search\":\"abc\",\"limit\":1}";
   1931 	unsigned char buf[1024];
   1932 	int i;
   1933 
   1934 	struct ndb_filter filter, *f = &filter;
   1935 	struct ndb_filter_elements *es;
   1936 
   1937 	ndb_filter_init_with(f, 1);
   1938 	assert(ndb_filter_from_json(json, strlen(json), f, buf, sizeof(buf)));
   1939 	assert(filter.finalized);
   1940 
   1941 	assert(f->num_elements == 2);
   1942 	for (i = 0; i < f->num_elements; i++) {
   1943 		es = ndb_filter_get_elements(f, i);
   1944 		if (i == 0) {
   1945 			assert(es->field.type == NDB_FILTER_SEARCH);
   1946 			assert(es->count == 1);
   1947 			assert(!strcmp(ndb_filter_get_string_element(f, es, 0), "abc"));
   1948 		} else if (i == 1) {
   1949 			assert(es->field.type == NDB_FILTER_LIMIT);
   1950 			assert(es->count == 1);
   1951 			assert(ndb_filter_get_int_element(es, 0) == 1);
   1952 		}
   1953 	}
   1954 
   1955 	// test back to json
   1956 	assert(ndb_filter_json(f, (char *)buf, sizeof(buf)));
   1957 	assert(!strcmp((const char*)buf, json));
   1958 
   1959 	ndb_filter_destroy(f);
   1960 }
   1961 
   1962 static void test_note_relay_index()
   1963 {
   1964 	const char *relay;
   1965 	struct ndb *ndb;
   1966 	struct ndb_txn txn;
   1967 	struct ndb_config config;
   1968 	struct ndb_filter filter, *f = &filter;
   1969 	uint64_t note_key, subid;
   1970 	struct ndb_ingest_meta meta;
   1971 
   1972 	const char *json = "[\"EVENT\",{\"id\": \"0f20295584a62d983a4fa85f7e50b460cd0049f94d8cd250b864bb822a747114\",\"pubkey\": \"55c882cf4a255ac66fc8507e718a1d1283ba46eb7d678d0573184dada1a4f376\",\"created_at\": 1742498339,\"kind\": 1,\"tags\": [],\"content\": \"hi\",\"sig\": \"ae1218280f554ea0b04ae09921031493d60fb7831dfd2dbd7086efeace2719a46842ce80342ebc002da8943df02e98b8b4abb4629c7103ca2114e6c4425f97fe\"}]";
   1973 
   1974 	// Initialize NDB
   1975 	ndb_default_config(&config);
   1976 	assert(ndb_init(&ndb, test_dir, &config));
   1977 
   1978 	// 1) Ingest the note from β€œrelay1”.
   1979 	// Use ndb_ingest_meta_init to record the relay.
   1980 
   1981 	assert(ndb_filter_init(f));
   1982 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
   1983 	assert(ndb_filter_add_int_element(f, 1));
   1984 	ndb_filter_end_field(f);
   1985 	ndb_filter_end(f);
   1986 
   1987 	assert((subid = ndb_subscribe(ndb, f, 1)));
   1988 
   1989 	ndb_ingest_meta_init(&meta, 1, "wss://relay.damus.io");
   1990 	assert(ndb_process_event_with(ndb, json, strlen(json), &meta));
   1991 	meta.relay = "wss://relay.mit.edu";
   1992 	assert(ndb_process_event_with(ndb, json, strlen(json), &meta));
   1993 	meta.relay = "wss://nostr.mom";
   1994 	assert(ndb_process_event_with(ndb, json, strlen(json), &meta));
   1995 	meta.relay = "ws://monad.jb55.com:8080";
   1996 	assert(ndb_process_event_with(ndb, json, strlen(json), &meta));
   1997 
   1998 	assert(ndb_wait_for_notes(ndb, subid, &note_key, 1) == 1);
   1999 	assert(note_key > 0);
   2000 
   2001 	// 4) Check that we have both relays
   2002 	assert(ndb_begin_query(ndb, &txn));
   2003 	assert(ndb_note_seen_on_relay(&txn, note_key, "wss://relay.damus.io"));
   2004 	assert(ndb_note_seen_on_relay(&txn, note_key, "wss://relay.mit.edu"));
   2005 	assert(ndb_note_seen_on_relay(&txn, note_key, "wss://nostr.mom"));
   2006 	assert(ndb_note_seen_on_relay(&txn, note_key, "ws://monad.jb55.com:8080"));
   2007 
   2008 	// walk the relays
   2009 	struct ndb_note_relay_iterator iter;
   2010 
   2011 	assert(ndb_note_relay_iterate_start(&txn, &iter, note_key));
   2012 
   2013 	relay = ndb_note_relay_iterate_next(&iter);
   2014 	assert(relay);
   2015 	assert(!strcmp(relay, "ws://monad.jb55.com:8080"));
   2016 
   2017 	relay = ndb_note_relay_iterate_next(&iter);
   2018 	assert(relay);
   2019 	assert(!strcmp(relay, "wss://nostr.mom"));
   2020 
   2021 	relay = ndb_note_relay_iterate_next(&iter);
   2022 	assert(relay);
   2023 	assert(!strcmp(relay, "wss://relay.damus.io"));
   2024 
   2025 	relay = ndb_note_relay_iterate_next(&iter);
   2026 	assert(relay);
   2027 	assert(!strcmp(relay, "wss://relay.mit.edu"));
   2028 
   2029 	assert(ndb_note_relay_iterate_next(&iter) == NULL);
   2030 	ndb_note_relay_iterate_close(&iter);
   2031 	assert(iter.mdb_cur == NULL);
   2032 
   2033 	assert(ndb_end_query(&txn));
   2034 
   2035 	// Cleanup
   2036 	ndb_filter_destroy(f);
   2037 	ndb_destroy(ndb);
   2038 
   2039 	printf("ok test_note_relay_index\n");
   2040 }
   2041 
   2042 static void test_nip50_profile_search() {
   2043 	struct ndb *ndb;
   2044 	struct ndb_txn txn;
   2045 	struct ndb_config config;
   2046 	struct ndb_filter filter, *f = &filter;
   2047 	int count;
   2048 	struct ndb_query_result result;
   2049 
   2050 	// Initialize NDB
   2051 	ndb_default_config(&config);
   2052 	assert(ndb_init(&ndb, test_dir, &config));
   2053 
   2054 	// 1) Ingest the note from β€œrelay1”.
   2055 	// Use ndb_ingest_meta_init to record the relay.
   2056 
   2057 	unsigned char expected_id[32] = {
   2058 	  0x22, 0x05, 0x0b, 0x6d, 0x97, 0xbb, 0x9d, 0xa0, 0x9e, 0x90, 0xed, 0x0c,
   2059 	  0x6d, 0xd9, 0x5e, 0xed, 0x1d, 0x42, 0x3e, 0x27, 0xd5, 0xcb, 0xa5, 0x94,
   2060 	  0xd2, 0xb4, 0xd1, 0x3a, 0x55, 0x43, 0x09, 0x07 };
   2061 	assert(ndb_filter_init(f));
   2062 	assert(ndb_filter_start_field(f, NDB_FILTER_SEARCH));
   2063 	assert(ndb_filter_add_str_element(f, "Selene"));
   2064 	ndb_filter_end_field(f);
   2065 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
   2066 	assert(ndb_filter_add_int_element(f, 0));
   2067 	ndb_filter_end_field(f);
   2068 	ndb_filter_end(f);
   2069 
   2070 	ndb_begin_query(ndb, &txn);
   2071 	ndb_query(&txn, f, 1, &result, 1, &count);
   2072 	ndb_end_query(&txn);
   2073 
   2074 	assert(count == 1);
   2075 	assert(!memcmp(ndb_note_id(result.note), expected_id, 32));
   2076 
   2077 	// Cleanup
   2078 	ndb_filter_destroy(f);
   2079 	ndb_destroy(ndb);
   2080 
   2081 	printf("ok test_nip50_profile_search\n");
   2082 }
   2083 
   2084 static bool only_threads_filter(void *ctx, struct ndb_note *note)
   2085 {
   2086 	struct ndb_iterator it;
   2087 	struct ndb_str str;
   2088 	const char *c;
   2089 
   2090 	ndb_tags_iterate_start(note, &it);
   2091 
   2092 	while (ndb_tags_iterate_next(&it)) {
   2093 		if (ndb_tag_count(it.tag) < 4)
   2094 			continue;
   2095 
   2096 		str = ndb_iter_tag_str(&it, 0);
   2097 		if (str.str[0] != 'e')
   2098 			continue;
   2099 
   2100 		str = ndb_iter_tag_str(&it, 3);
   2101 		if (str.flag == NDB_PACKED_ID)
   2102 			continue;
   2103 
   2104 		if (str.str[0] != 'r')
   2105 			continue;
   2106 
   2107 		// if it has a reply or root marker, then this is a reply
   2108 		c = &str.str[1];
   2109 		if (*c++ == 'e' && *c++ == 'p' && *c++ == 'l' && *c++ == 'y' && *c++ == '\0')  {
   2110 			return false;
   2111 		}
   2112 
   2113 		c = &str.str[1];
   2114 		if (*c++ == 'o' && *c++ == 'o' && *c++ == 't' && *c++ == '\0') {
   2115 			return false;
   2116 		}
   2117 	}
   2118 
   2119 	return true;
   2120 }
   2121 
   2122 static void test_custom_filter()
   2123 {
   2124 	struct ndb *ndb;
   2125 	struct ndb_txn txn;
   2126 	struct ndb_config config;
   2127 	struct ndb_filter filter, *f = &filter;
   2128 	struct ndb_filter filter2, *f2 = &filter2;
   2129 	int count, nres = 2;
   2130 	uint64_t sub_id, note_keys[2];
   2131 	struct ndb_query_result results[2];
   2132 	struct ndb_ingest_meta meta;
   2133 
   2134 	const char *root = "[\"EVENT\",{\"id\":\"3d3fba391ce6f83cf336b161f3de90bb2610c20dfb9f4de3a6dacb6b11362971\",\"pubkey\":\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"created_at\":1744084064,\"kind\":1,\"tags\":[],\"content\":\"dire wolves are back LFG\",\"sig\":\"340a6ee8a859a1d78e50551dae7b24aaba7137647a6ac295acc97faa06ba33310593f5b4081dad188aee81266144f2312afb249939a2e07c14ca167af08e998f\"}]";
   2135 
   2136 	const char *reply_json = "[\"EVENT\",{\"id\":\"3a338522ee1e27056acccee65849de8deba426db1c71cbd61d105280bbb67ed2\",\"pubkey\":\"7cc328a08ddb2afdf9f9be77beff4c83489ff979721827d628a542f32a247c0e\",\"created_at\":1744151551,\"kind\":1,\"tags\":[[\"alt\",\"A short note: pokerdeck β™ οΈπŸ¦πŸ§\"],[\"e\",\"086ccb1873fa10d4338713f24b034e17e543d8ad79c15ff39cf59f4d0cb7a2d6\",\"wss://nostr.wine/\",\"root\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"wss://nostr.wine\"]],\"content\":\"pokerdeck β™ οΈπŸ¦πŸ§\",\"sig\":\"1099e47ba1ba962a8f2617c139e240d0369d81e70241dce7e73340e3230d8a3039c07114ed21f0e765e40f1b71fc2770fa5585994d27d2ece3b14e7b98f988d3\"}]";
   2137 
   2138 	ndb_default_config(&config);
   2139 	assert(ndb_init(&ndb, test_dir, &config));
   2140 
   2141 	ndb_filter_init(f);
   2142 	ndb_filter_start_field(f, NDB_FILTER_KINDS);
   2143 	ndb_filter_add_int_element(f, 1);
   2144 	ndb_filter_end_field(f);
   2145 
   2146 	ndb_filter_start_field(f, NDB_FILTER_CUSTOM);
   2147 	ndb_filter_add_custom_filter_element(f, only_threads_filter, NULL);
   2148 	ndb_filter_end_field(f);
   2149 	ndb_filter_end(f);
   2150 
   2151 	assert(ndb_filter_init(f2));
   2152 	assert(ndb_filter_start_field(f2, NDB_FILTER_KINDS));
   2153 	assert(ndb_filter_add_int_element(f2, 1));
   2154 	ndb_filter_end_field(f2);
   2155 	ndb_filter_end(f2);
   2156 
   2157 	sub_id = ndb_subscribe(ndb, f2, 1);
   2158 
   2159 	ndb_ingest_meta_init(&meta, 1, "test");
   2160 
   2161 	assert(ndb_process_event_with(ndb, root, strlen(root), &meta));
   2162 	assert(ndb_process_event_with(ndb, reply_json, strlen(reply_json), &meta));
   2163 
   2164 	for (nres = 2; nres > 0;)
   2165 		nres -= ndb_wait_for_notes(ndb, sub_id, note_keys, 2);
   2166 
   2167 	ndb_begin_query(ndb, &txn);
   2168 	ndb_query(&txn, f, 1, results, 2, &count);
   2169 	ndb_end_query(&txn);
   2170 
   2171 	assert(count == 1);
   2172 	assert(ndb_note_id(results[0].note)[0] == 0x3d);
   2173 
   2174 	// Cleanup
   2175 	ndb_filter_destroy(f);
   2176 	ndb_filter_destroy(f2);
   2177 	ndb_destroy(ndb);
   2178 	delete_test_db();
   2179 
   2180 	printf("ok test_custom_filter\n");
   2181 }
   2182 
   2183 void test_replay_attack() {
   2184 	struct ndb_filter filter, *f = &filter;
   2185 	struct ndb *ndb;
   2186 	struct ndb_txn txn;
   2187 	struct ndb_config config;
   2188 	uint64_t note_key, sub_id;
   2189 	int count;
   2190 	struct ndb_query_result results[2];
   2191 	unsigned char expected[] = { 0x1f, 0x5f, 0x21, 0xb2, 0x2e, 0x4c, 0x87, 0xb1, 0xd7, 0xcf, 0x92, 0x71, 0xa1, 0xc8, 0xae, 0xaf, 0x5b, 0x49, 0x06, 0x1a, 0xf2, 0xc0, 0x3c, 0xab, 0x70, 0x6b, 0xbe, 0x2e, 0xbb, 0x9f, 0xef, 0xd9 };
   2192 
   2193 	// maleated note
   2194 	const char *bad_note = "[\"EVENT\",\"blah\",{\"id\":\"3d3fba391ce6f83cf336b161f3de90bb2610c20dfb9f4de3a6dacb6b11362971\",\"pubkey\":\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"created_at\":1744084064,\"kind\":1,\"tags\":[],\"content\":\"dire wolves are cool\",\"sig\":\"340a6ee8a859a1d78e50551dae7b24aaba7137647a6ac295acc97faa06ba33310593f5b4081dad188aee81266144f2312afb249939a2e07c14ca167af08e998f\"}]";
   2195 
   2196 	const char *ok_note = "[\"EVENT\",\"blah\",{\"id\": \"1f5f21b22e4c87b1d7cf9271a1c8aeaf5b49061af2c03cab706bbe2ebb9fefd9\",\"pubkey\": \"73764df506728297a9c1f359024d2f9c895001f4afda2d0afa844ce7a94778ca\",\"created_at\": 1750785986,\"kind\": 1,\"tags\": [],\"content\": \"ok\",\"sig\": \"bde9ee6933b01c11a9881a3ea4730c6e7f7d952c165fb7ab3f77488c5f73e6d60ce25a32386f2e1d0244c24fd840f25af5d2d04dc6d229ec0f67c7782e8879d9\"}]";
   2197 
   2198 	ndb_default_config(&config);
   2199 	assert(ndb_init(&ndb, test_dir, &config));
   2200 
   2201 	ndb_filter_init(f);
   2202 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
   2203 	assert(ndb_filter_add_int_element(f, 1));
   2204 	ndb_filter_end_field(f);
   2205 	ndb_filter_end(f);
   2206 
   2207 	sub_id = ndb_subscribe(ndb, f, 1);
   2208 
   2209 	ndb_process_event(ndb, bad_note, strlen(bad_note));
   2210 	ndb_process_event(ndb, ok_note, strlen(bad_note));
   2211 
   2212 	assert(ndb_wait_for_notes(ndb, sub_id, &note_key, 1) == 1);
   2213 
   2214 	ndb_begin_query(ndb, &txn);
   2215 	ndb_query(&txn, f, 1, results, 2, &count);
   2216 	ndb_end_query(&txn);
   2217 
   2218 	assert(count == 1);
   2219 	assert(memcmp(expected, ndb_note_id(results[0].note), 32) == 0);
   2220 
   2221 	ndb_filter_destroy(f);
   2222 	ndb_destroy(ndb);
   2223 	delete_test_db();
   2224 }
   2225 
   2226 int main(int argc, const char *argv[]) {
   2227 	delete_test_db();
   2228 
   2229 	test_replay_attack();
   2230 	test_custom_filter();
   2231 	test_metadata();
   2232 	test_count_metadata();
   2233 	test_reaction_encoding();
   2234 	test_reaction_counter();
   2235 	test_note_relay_index();
   2236 	test_filter_search();
   2237 	test_filter_parse_search_json();
   2238 	test_parse_filter_json();
   2239 	test_filter_eq();
   2240 	test_filter_is_subset();
   2241 	test_filter_json();
   2242 	test_bech32_parsing();
   2243 	test_single_url_parsing();
   2244 	test_url_parsing();
   2245 	test_query();
   2246 	test_tag_query();
   2247 	test_weird_note_corruption();
   2248 	test_parse_content();
   2249 	test_subscriptions();
   2250 	test_comma_url_parsing();
   2251 	test_varints();
   2252 	test_bech32_objects();
   2253 	//test_block_coding();
   2254 	test_encode_decode_invoice();
   2255 	test_filters();
   2256 	//test_migrate();
   2257 	test_fetched_at();
   2258 	test_profile_updates();
   2259 	test_load_profiles();
   2260 	test_nip50_profile_search();
   2261 	test_basic_event();
   2262 	test_empty_tags();
   2263 	test_parse_json();
   2264 	test_parse_contact_list();
   2265 	test_strings_work_before_finalization();
   2266 	test_tce();
   2267 	test_tce_command_result();
   2268 	test_tce_eose();
   2269 	test_tce_command_result_empty_msg();
   2270 	test_content_len();
   2271 	test_fuzz_events();
   2272 
   2273 	// note fetching
   2274 	test_fetch_last_noteid();
   2275 
   2276 	test_timeline_query();
   2277 
   2278 	// fulltext
   2279 	test_fulltext();
   2280 
   2281 	// protected queue tests
   2282 	test_queue_init_pop_push();
   2283 	test_queue_thread_safety();
   2284 	test_queue_boundary_conditions();
   2285 
   2286 	// memchr stuff
   2287 	test_fast_strchr();
   2288 
   2289 	// profiles
   2290 	test_replacement();
   2291 
   2292 	printf("All tests passed!\n");       // Print this if all tests pass.
   2293 }
   2294 
   2295 
   2296