nostrdb

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

test.c (55748B)


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