nostrdb

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

test.c (69365B)


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