nostrdb

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

test.c (57991B)


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