damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

test.c (30758B)


      1 
      2 #include "nostrdb.h"
      3 #include "hex.h"
      4 #include "io.h"
      5 #include "bolt11/bolt11.h"
      6 #include "bolt11/amount.h"
      7 #include "protected_queue.h"
      8 #include "memchr.h"
      9 #include "print_util.h"
     10 #include "bindings/c/profile_reader.h"
     11 #include "bindings/c/profile_verifier.h"
     12 #include "bindings/c/meta_reader.h"
     13 #include "bindings/c/meta_verifier.h"
     14 
     15 #include <stdio.h>
     16 #include <assert.h>
     17 #include <unistd.h>
     18 
     19 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
     20 
     21 static const char *test_dir = "./testdata/db";
     22 
     23 static NdbProfile_table_t lookup_profile(struct ndb_txn *txn, uint64_t pk)
     24 {
     25 	void *root;
     26 	size_t len;
     27 	assert((root = ndb_get_profile_by_key(txn, pk, &len)));
     28 	assert(root);
     29 
     30 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(root);
     31 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
     32 	return profile;
     33 }
     34 
     35 static void print_search(struct ndb_txn *txn, struct ndb_search *search)
     36 {
     37 	NdbProfile_table_t profile = lookup_profile(txn, search->profile_key);
     38 	const char *name = NdbProfile_name_get(profile);
     39 	const char *display_name = NdbProfile_display_name_get(profile);
     40 	printf("searched_name name:'%s' display_name:'%s' pk:%" PRIu64 " ts:%" PRIu64 " id:", name, display_name, search->profile_key, search->key->timestamp);
     41 	print_hex(search->key->id, 32);
     42 	printf("\n");
     43 }
     44 
     45 
     46 static void test_filters()
     47 {
     48 	struct ndb_filter filter, *f;
     49 	struct ndb_note *note;
     50 	unsigned char buffer[4096];
     51 
     52 	const char *test_note = "{\"id\": \"160e76ca67405d7ce9ef7d2dd72f3f36401c8661a73d45498af842d40b01b736\",\"pubkey\": \"67c67870aebc327eb2a2e765e6dbb42f0f120d2c4e4e28dc16b824cf72a5acc1\",\"created_at\": 1700688516,\"kind\": 1337,\"tags\": [[\"t\",\"hashtag\"],[\"t\",\"grownostr\"],[\"p\",\"4d2e7a6a8e08007ace5a03391d21735f45caf1bf3d67b492adc28967ab46525e\"]],\"content\": \"\",\"sig\": \"20c2d070261ed269559ada40ca5ac395c389681ee3b5f7d50de19dd9b328dd70cf27d9d13875e87c968d9b49fa05f66e90f18037be4529b9e582c7e2afac3f06\"}";
     53 
     54 	f = &filter;
     55 	assert(ndb_note_from_json(test_note, strlen(test_note), &note, buffer, sizeof(buffer)));
     56 
     57 	assert(ndb_filter_init(f));
     58 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
     59 	assert(ndb_filter_add_int_element(f, 1337));
     60 	assert(ndb_filter_add_int_element(f, 2));
     61 
     62 	assert(f->current->count == 2);
     63 	assert(f->current->field.type == NDB_FILTER_KINDS);
     64 
     65 	// can't start if we've already started
     66 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS) == 0);
     67 	assert(ndb_filter_start_field(f, NDB_FILTER_GENERIC) == 0);
     68 	ndb_filter_end_field(f);
     69 
     70 	// try matching the filter
     71 	assert(ndb_filter_matches(f, note));
     72 
     73 	_ndb_note_set_kind(note, 1);
     74 
     75 	// inverse match
     76 	assert(!ndb_filter_matches(f, note));
     77 
     78 	// should also match 2
     79 	_ndb_note_set_kind(note, 2);
     80 	assert(ndb_filter_matches(f, note));
     81 
     82 	// don't free, just reset data pointers
     83 	ndb_filter_reset(f);
     84 
     85 	// now try generic matches
     86 	assert(ndb_filter_start_generic_field(f, 't'));
     87 	assert(ndb_filter_add_str_element(f, "grownostr"));
     88 	ndb_filter_end_field(f);
     89 	assert(ndb_filter_start_field(f, NDB_FILTER_KINDS));
     90 	assert(ndb_filter_add_int_element(f, 3));
     91 	ndb_filter_end_field(f);
     92 
     93 	// shouldn't match the kind filter
     94 	assert(!ndb_filter_matches(f, note));
     95 
     96 	_ndb_note_set_kind(note, 3);
     97 
     98 	// now it should
     99 	assert(ndb_filter_matches(f, note));
    100 
    101 	ndb_filter_reset(f);
    102 	assert(ndb_filter_start_field(f, NDB_FILTER_AUTHORS));
    103 	assert(ndb_filter_add_id_element(f, ndb_note_pubkey(note)));
    104 	ndb_filter_end_field(f);
    105 	assert(f->current == NULL);
    106 	assert(ndb_filter_matches(f, note));
    107 
    108 	ndb_filter_free(f);
    109 }
    110 
    111 // Test fetched_at profile records. These are saved when new profiles are
    112 // processed, or the last time we've fetched the profile.
    113 static void test_fetched_at()
    114 {
    115 	struct ndb *ndb;
    116 	struct ndb_txn txn;
    117 	uint64_t fetched_at, t1, t2;
    118 	struct ndb_config config;
    119 	ndb_default_config(&config);
    120 
    121 	assert(ndb_init(&ndb, test_dir, &config));
    122 
    123 	const unsigned char pubkey[] = { 0x87, 0xfb, 0xc6, 0xd5, 0x98, 0x31, 0xa8, 0x23, 0xa4, 0x5d, 0x10, 0x1f,
    124   0x86, 0x94, 0x2c, 0x41, 0xcd, 0xe2, 0x90, 0x23, 0xf4, 0x09, 0x20, 0x24,
    125   0xa2, 0x7c, 0x50, 0x10, 0x3c, 0x15, 0x40, 0x01 };
    126 
    127 	const char profile_1[] = "[\"EVENT\",{\"id\": \"a44eb8fb6931d6155b04038bef0624407e46c85c61e5758392cbb615f00184ca\",\"pubkey\": \"87fbc6d59831a823a45d101f86942c41cde29023f4092024a27c50103c154001\",\"created_at\": 1695593354,\"kind\": 0,\"tags\": [],\"content\": \"{\\\"name\\\":\\\"b\\\"}\",\"sig\": \"7540bbde4b4479275e20d95acaa64027359a73989927f878825093cba2f468bd8e195919a77b4c230acecddf92e6b4bee26918b0c0842f84ec7c1fae82453906\"}]";
    128 
    129 	t1 = time(NULL);
    130 
    131 	// process the first event, this should set the fetched_at
    132 	assert(ndb_process_client_event(ndb, profile_1, sizeof(profile_1)));
    133 
    134 	// we sleep for a second because we want to make sure the fetched_at is not
    135 	// updated for the next record, which is an older profile.
    136 	sleep(1);
    137 
    138 	assert(ndb_begin_query(ndb, &txn));
    139 
    140 	// this should be set to t1
    141 	fetched_at = ndb_read_last_profile_fetch(&txn, pubkey);
    142 
    143 	assert(fetched_at == t1);
    144 
    145 	t2 = time(NULL);
    146 	assert(t1 != t2); // sanity
    147 
    148 	const char profile_2[] = "[\"EVENT\",{\"id\": \"9b2861dda8fc602ec2753f92f1a443c9565de606e0c8f4fd2db4f2506a3b13ca\",\"pubkey\": \"87fbc6d59831a823a45d101f86942c41cde29023f4092024a27c50103c154001\",\"created_at\": 1695593347,\"kind\": 0,\"tags\": [],\"content\": \"{\\\"name\\\":\\\"a\\\"}\",\"sig\": \"f48da228f8967d33c3caf0a78f853b5144631eb86c7777fd25949123a5272a92765a0963d4686dd0efe05b7a9b986bfac8d43070b234153acbae5006d5a90f31\"}]";
    149 
    150 	t2 = time(NULL);
    151 
    152 	// process the second event, since this is older it should not change
    153 	// fetched_at
    154 	assert(ndb_process_client_event(ndb, profile_2, sizeof(profile_2)));
    155 
    156 	// we sleep for a second because we want to make sure the fetched_at is not
    157 	// updated for the next record, which is an older profile.
    158 	sleep(1);
    159 
    160 	fetched_at = ndb_read_last_profile_fetch(&txn, pubkey);
    161 	assert(fetched_at == t1);
    162 }
    163 
    164 static void test_reaction_counter()
    165 {
    166 	static const int alloc_size = 1024 * 1024;
    167 	char *json = malloc(alloc_size);
    168 	struct ndb *ndb;
    169 	size_t len;
    170 	void *root;
    171 	int written, reactions;
    172 	NdbEventMeta_table_t meta;
    173 	struct ndb_txn txn;
    174 	struct ndb_config config;
    175 	ndb_default_config(&config);
    176 
    177 	assert(ndb_init(&ndb, test_dir, &config));
    178 
    179 	read_file("testdata/reactions.json", (unsigned char*)json, alloc_size, &written);
    180 	assert(ndb_process_client_events(ndb, json, written));
    181 	ndb_destroy(ndb);
    182 
    183 	assert(ndb_init(&ndb, test_dir, &config));
    184 
    185 	assert(ndb_begin_query(ndb, &txn));
    186 
    187 	const unsigned char id[32] = {
    188 	  0x1a, 0x41, 0x56, 0x30, 0x31, 0x09, 0xbb, 0x4a, 0x66, 0x0a, 0x6a, 0x90,
    189 	  0x04, 0xb0, 0xcd, 0xce, 0x8d, 0x83, 0xc3, 0x99, 0x1d, 0xe7, 0x86, 0x4f,
    190 	  0x18, 0x76, 0xeb, 0x0f, 0x62, 0x2c, 0x68, 0xe8
    191 	};
    192 
    193 	assert((root = ndb_get_note_meta(&txn, id, &len)));
    194 	assert(0 == NdbEventMeta_verify_as_root(root, len));
    195 	assert((meta = NdbEventMeta_as_root(root)));
    196 
    197 	reactions = NdbEventMeta_reactions_get(meta);
    198 	//printf("counted reactions: %d\n", reactions);
    199 	assert(reactions == 2);
    200 	ndb_end_query(&txn);
    201 	ndb_destroy(ndb);
    202 }
    203 
    204 static void test_profile_search(struct ndb *ndb)
    205 {
    206 	struct ndb_txn txn;
    207 	struct ndb_search search;
    208 	int i;
    209 	const char *name;
    210 	NdbProfile_table_t profile;
    211 
    212 	assert(ndb_begin_query(ndb, &txn));
    213 	assert(ndb_search_profile(&txn, &search, "jean"));
    214 	//print_search(&txn, &search);
    215 	profile = lookup_profile(&txn, search.profile_key);
    216 	name = NdbProfile_name_get(profile);
    217 	assert(!strncmp(name, "jean", 4));
    218 
    219 	assert(ndb_search_profile_next(&search));
    220 	//print_search(&txn, &search);
    221 	profile = lookup_profile(&txn, search.profile_key);
    222 	name = NdbProfile_name_get(profile);
    223 	//assert(strncmp(name, "jean", 4));
    224 
    225 	for (i = 0; i < 3; i++) {
    226 		ndb_search_profile_next(&search);
    227 		//print_search(&txn, &search);
    228 	}
    229 
    230 	//assert(!strcmp(name, "jb55"));
    231 
    232 	ndb_search_profile_end(&search);
    233 	ndb_end_query(&txn);
    234 }
    235 
    236 static void test_profile_updates()
    237 {
    238 	static const int alloc_size = 1024 * 1024;
    239 	char *json = malloc(alloc_size);
    240 	struct ndb *ndb;
    241 	size_t len;
    242 	void *record;
    243 	int written;
    244 	struct ndb_txn txn;
    245 	uint64_t key;
    246 	struct ndb_config config;
    247 	ndb_default_config(&config);
    248 
    249 	assert(ndb_init(&ndb, test_dir, &config));
    250 
    251 	read_file("testdata/profile-updates.json", (unsigned char*)json, alloc_size, &written);
    252 
    253 	assert(ndb_process_client_events(ndb, json, written));
    254 
    255 	ndb_destroy(ndb);
    256 
    257 	assert(ndb_init(&ndb, test_dir, &config));
    258 
    259 	assert(ndb_begin_query(ndb, &txn));
    260 	const unsigned char pk[32] = {
    261 		0x87, 0xfb, 0xc6, 0xd5, 0x98, 0x31, 0xa8, 0x23, 0xa4, 0x5d,
    262 		0x10, 0x1f, 0x86, 0x94, 0x2c, 0x41, 0xcd, 0xe2, 0x90, 0x23,
    263 		0xf4, 0x09, 0x20, 0x24, 0xa2, 0x7c, 0x50, 0x10, 0x3c, 0x15,
    264 		0x40, 0x01
    265 	};
    266 	record = ndb_get_profile_by_pubkey(&txn, pk, &len, &key);
    267 
    268 	assert(record);
    269 	int res = NdbProfileRecord_verify_as_root(record, len);
    270 	assert(res == 0);
    271 
    272 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(record);
    273 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
    274 	const char *name = NdbProfile_name_get(profile);
    275 
    276 	assert(!strcmp(name, "c"));
    277 
    278 	ndb_destroy(ndb);
    279 }
    280 
    281 static void test_load_profiles()
    282 {
    283 	static const int alloc_size = 1024 * 1024;
    284 	char *json = malloc(alloc_size);
    285 	struct ndb *ndb;
    286 	int written;
    287 	struct ndb_config config;
    288 	ndb_default_config(&config);
    289 
    290 	assert(ndb_init(&ndb, test_dir, &config));
    291 
    292 	read_file("testdata/profiles.json", (unsigned char*)json, alloc_size, &written);
    293 
    294 	assert(ndb_process_events(ndb, json, written));
    295 
    296 	ndb_destroy(ndb);
    297 
    298 	assert(ndb_init(&ndb, test_dir, &config));
    299 	unsigned char id[32] = {
    300 	  0x22, 0x05, 0x0b, 0x6d, 0x97, 0xbb, 0x9d, 0xa0, 0x9e, 0x90, 0xed, 0x0c,
    301 	  0x6d, 0xd9, 0x5e, 0xed, 0x1d, 0x42, 0x3e, 0x27, 0xd5, 0xcb, 0xa5, 0x94,
    302 	  0xd2, 0xb4, 0xd1, 0x3a, 0x55, 0x43, 0x09, 0x07 };
    303 	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\"}";
    304 
    305 	struct ndb_txn txn;
    306 	assert(ndb_begin_query(ndb, &txn));
    307 	struct ndb_note *note = ndb_get_note_by_id(&txn, id, NULL, NULL);
    308 	assert(note != NULL);
    309 	assert(!strcmp(ndb_note_content(note), expected_content));
    310 	ndb_end_query(&txn);
    311 
    312 	test_profile_search(ndb);
    313 
    314 	ndb_destroy(ndb);
    315 
    316 	free(json);
    317 }
    318 
    319 static void test_fuzz_events() {
    320 	struct ndb *ndb;
    321 	const char *str = "[\"EVENT\"\"\"{\"content\"\"created_at\":0 \"id\"\"5086a8f76fe1da7fb56a25d1bebbafd70fca62e36a72c6263f900ff49b8f8604\"\"kind\":0 \"pubkey\":9c87f94bcbe2a837adc28d46c34eeaab8fc2e1cdf94fe19d4b99ae6a5e6acedc \"sig\"\"27374975879c94658412469cee6db73d538971d21a7b580726a407329a4cafc677fb56b946994cea59c3d9e118fef27e4e61de9d2c46ac0a65df14153 ea93cf5\"\"tags\"[[][\"\"]]}]";
    322 	struct ndb_config config;
    323 	ndb_default_config(&config);
    324 
    325 	ndb_init(&ndb, test_dir, &config);
    326 	ndb_process_event(ndb, str, strlen(str));
    327 	ndb_destroy(ndb);
    328 }
    329 
    330 static void test_migrate() {
    331 	static const char *v0_dir = "testdata/db/v0";
    332 	struct ndb *ndb;
    333 	struct ndb_config config;
    334 	ndb_default_config(&config);
    335 	ndb_config_set_flags(&config, NDB_FLAG_NOMIGRATE);
    336 
    337 	fprintf(stderr, "testing migrate on v0\n");
    338 	assert(ndb_init(&ndb, v0_dir, &config));
    339 	assert(ndb_db_version(ndb) == 0);
    340 	ndb_destroy(ndb);
    341 
    342 	ndb_config_set_flags(&config, 0);
    343 
    344 	assert(ndb_init(&ndb, v0_dir, &config));
    345 	ndb_destroy(ndb);
    346 	assert(ndb_init(&ndb, v0_dir, &config));
    347 	assert(ndb_db_version(ndb) == 3);
    348 
    349 	test_profile_search(ndb);
    350 	ndb_destroy(ndb);
    351 }
    352 
    353 static void test_basic_event() {
    354 	unsigned char buf[512];
    355 	struct ndb_builder builder, *b = &builder;
    356 	struct ndb_note *note;
    357 	int ok;
    358 
    359 	unsigned char id[32];
    360 	memset(id, 1, 32);
    361 
    362 	unsigned char pubkey[32];
    363 	memset(pubkey, 2, 32);
    364 
    365 	unsigned char sig[64];
    366 	memset(sig, 3, 64);
    367 
    368 	const char *hex_pk = "5d9b81b2d4d5609c5565286fc3b511dc6b9a1b3d7d1174310c624d61d1f82bb9";
    369 
    370 	ok = ndb_builder_init(b, buf, sizeof(buf));
    371 	assert(ok);
    372 	note = builder.note;
    373 
    374 	//memset(note->padding, 3, sizeof(note->padding));
    375 
    376 	ok = ndb_builder_set_content(b, hex_pk, strlen(hex_pk)); assert(ok);
    377 	ndb_builder_set_id(b, id); assert(ok);
    378 	ndb_builder_set_pubkey(b, pubkey); assert(ok);
    379 	ndb_builder_set_sig(b, sig); assert(ok);
    380 
    381 	ok = ndb_builder_new_tag(b); assert(ok);
    382 	ok = ndb_builder_push_tag_str(b, "p", 1); assert(ok);
    383 	ok = ndb_builder_push_tag_str(b, hex_pk, 64); assert(ok);
    384 
    385 	ok = ndb_builder_new_tag(b); assert(ok);
    386 	ok = ndb_builder_push_tag_str(b, "word", 4); assert(ok);
    387 	ok = ndb_builder_push_tag_str(b, "words", 5); assert(ok);
    388 	ok = ndb_builder_push_tag_str(b, "w", 1); assert(ok);
    389 
    390 	ok = ndb_builder_finalize(b, &note, NULL);
    391 	assert(ok);
    392 
    393 	// content should never be packed id
    394 	// TODO: figure out how to test this now that we don't expose it
    395 	// assert(note->content.packed.flag != NDB_PACKED_ID);
    396 	assert(ndb_tags_count(ndb_note_tags(note)) == 2);
    397 
    398 	// test iterator
    399 	struct ndb_iterator iter, *it = &iter;
    400 	
    401 	ndb_tags_iterate_start(note, it);
    402 	ok = ndb_tags_iterate_next(it);
    403 	assert(ok);
    404 
    405 	assert(ndb_tag_count(it->tag) == 2);
    406 	const char *p      = ndb_iter_tag_str(it, 0).str;
    407 	struct ndb_str hpk = ndb_iter_tag_str(it, 1);
    408 
    409 	hex_decode(hex_pk, 64, id, 32);
    410 
    411 	assert(hpk.flag == NDB_PACKED_ID);
    412 	assert(memcmp(hpk.id, id, 32) == 0);
    413 	assert(!strcmp(p, "p"));
    414 
    415 	ok = ndb_tags_iterate_next(it);
    416 	assert(ok);
    417 	assert(ndb_tag_count(it->tag) == 3);
    418 	assert(!strcmp(ndb_iter_tag_str(it, 0).str, "word"));
    419 	assert(!strcmp(ndb_iter_tag_str(it, 1).str, "words"));
    420 	assert(!strcmp(ndb_iter_tag_str(it, 2).str, "w"));
    421 
    422 	ok = ndb_tags_iterate_next(it);
    423 	assert(!ok);
    424 }
    425 
    426 static void test_empty_tags() {
    427 	struct ndb_builder builder, *b = &builder;
    428 	struct ndb_iterator iter, *it = &iter;
    429 	struct ndb_note *note;
    430 	int ok;
    431 	unsigned char buf[1024];
    432 
    433 	ok = ndb_builder_init(b, buf, sizeof(buf));
    434 	assert(ok);
    435 
    436 	ok = ndb_builder_finalize(b, &note, NULL);
    437 	assert(ok);
    438 
    439 	assert(ndb_tags_count(ndb_note_tags(note)) == 0);
    440 
    441 	ndb_tags_iterate_start(note, it);
    442 	ok = ndb_tags_iterate_next(it);
    443 	assert(!ok);
    444 }
    445 
    446 static void print_tag(struct ndb_note *note, struct ndb_tag *tag) {
    447 	struct ndb_str str;
    448 	int tag_count = ndb_tag_count(tag);
    449 	for (int i = 0; i < tag_count; i++) {
    450 		str = ndb_tag_str(note, tag, i);
    451 		if (str.flag == NDB_PACKED_ID) {
    452 			printf("<id> ");
    453 		} else {
    454 			printf("%s ", str.str);
    455 		}
    456 	}
    457 	printf("\n");
    458 }
    459 
    460 static void test_parse_contact_list()
    461 {
    462 	int size, written = 0;
    463 	unsigned char id[32];
    464 	static const int alloc_size = 2 << 18;
    465 	unsigned char *json = malloc(alloc_size);
    466 	unsigned char *buf = malloc(alloc_size);
    467 	struct ndb_note *note;
    468 
    469 	read_file("testdata/contacts.json", json, alloc_size, &written);
    470 
    471 	size = ndb_note_from_json((const char*)json, written, &note, buf, alloc_size);
    472 	printf("ndb_note_from_json size %d\n", size);
    473 	assert(size > 0);
    474 	assert(size == 34328);
    475 
    476 	memcpy(id, ndb_note_id(note), 32);
    477 	memset(ndb_note_id(note), 0, 32);
    478 	assert(ndb_calculate_id(note, json, alloc_size));
    479 	assert(!memcmp(ndb_note_id(note), id, 32));
    480 
    481 	const char* expected_content = 
    482 	"{\"wss://nos.lol\":{\"write\":true,\"read\":true},"
    483 	"\"wss://relay.damus.io\":{\"write\":true,\"read\":true},"
    484 	"\"ws://monad.jb55.com:8080\":{\"write\":true,\"read\":true},"
    485 	"\"wss://nostr.wine\":{\"write\":true,\"read\":true},"
    486 	"\"wss://welcome.nostr.wine\":{\"write\":true,\"read\":true},"
    487 	"\"wss://eden.nostr.land\":{\"write\":true,\"read\":true},"
    488 	"\"wss://relay.mostr.pub\":{\"write\":true,\"read\":true},"
    489 	"\"wss://nostr-pub.wellorder.net\":{\"write\":true,\"read\":true}}";
    490 
    491 	assert(!strcmp(expected_content, ndb_note_content(note)));
    492 	assert(ndb_note_created_at(note) == 1689904312);
    493 	assert(ndb_note_kind(note) == 3);
    494 	assert(ndb_tags_count(ndb_note_tags(note)) == 786);
    495 	//printf("note content length %d\n", ndb_note_content_length(note));
    496 	printf("ndb_content_len %d, expected_len %ld\n",
    497 			ndb_note_content_length(note),
    498 			strlen(expected_content));
    499 	assert(ndb_note_content_length(note) == strlen(expected_content));
    500 
    501 	struct ndb_iterator iter, *it = &iter;
    502 	ndb_tags_iterate_start(note, it);
    503 
    504 	int tags = 0;
    505 	int total_elems = 0;
    506 
    507 	while (ndb_tags_iterate_next(it)) {
    508 		total_elems += ndb_tag_count(it->tag);
    509 		//printf("tag %d: ", tags);
    510 		if (tags == 0 || tags == 1 || tags == 2)
    511 			assert(ndb_tag_count(it->tag) == 3);
    512 
    513 		if (tags == 6)
    514 			assert(ndb_tag_count(it->tag) == 2);
    515 
    516 		if (tags == 7)
    517 			assert(!strcmp(ndb_tag_str(note, it->tag, 2).str, "wss://nostr-pub.wellorder.net"));
    518 
    519 		if (tags == 786) {
    520 			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 };
    521 			assert(!memcmp(ndb_tag_str(note, it->tag, 1).id, h, 32));
    522 		}
    523 
    524 		//print_tag(it->note, it->tag);
    525 
    526 		tags += 1;
    527 	}
    528 
    529 	assert(tags == 786);
    530 	//printf("total_elems %d\n", total_elems);
    531 	assert(total_elems == 1580);
    532 
    533 	write_file("test_contacts_ndb_note", (unsigned char *)note, size);
    534 	printf("wrote test_contacts_ndb_note (raw ndb_note)\n");
    535 
    536 	free(json);
    537 	free(buf);
    538 }
    539 
    540 static void test_replacement()
    541 {
    542 	static const int alloc_size = 1024 * 1024;
    543 	char *json = malloc(alloc_size);
    544 	unsigned char *buf = malloc(alloc_size);
    545 	struct ndb *ndb;
    546 	size_t len;
    547 	int written;
    548 	struct ndb_config config;
    549 	ndb_default_config(&config);
    550 
    551 	assert(ndb_init(&ndb, test_dir, &config));
    552 
    553 	read_file("testdata/old-new.json", (unsigned char*)json, alloc_size, &written);
    554 	assert(ndb_process_events(ndb, json, written));
    555 
    556 	ndb_destroy(ndb);
    557 	assert(ndb_init(&ndb, test_dir, &config));
    558 
    559 	struct ndb_txn txn;
    560 	assert(ndb_begin_query(ndb, &txn));
    561 
    562 	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 };
    563 
    564 	void *root = ndb_get_profile_by_pubkey(&txn, pubkey, &len, NULL);
    565 
    566 	assert(root);
    567 	int res = NdbProfileRecord_verify_as_root(root, len);
    568 	assert(res == 0);
    569 
    570 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(root);
    571 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
    572 	const char *name = NdbProfile_name_get(profile);
    573 
    574 	assert(!strcmp(name, "jb55"));
    575 
    576 	ndb_end_query(&txn);
    577 
    578 	free(json);
    579 	free(buf);
    580 }
    581 
    582 static void test_fetch_last_noteid()
    583 {
    584 	static const int alloc_size = 1024 * 1024;
    585 	char *json = malloc(alloc_size);
    586 	unsigned char *buf = malloc(alloc_size);
    587 	struct ndb *ndb;
    588 	size_t len;
    589 	int written;
    590 	struct ndb_config config;
    591 	ndb_default_config(&config);
    592 
    593 	assert(ndb_init(&ndb, test_dir, &config));
    594 
    595 	read_file("testdata/random.json", (unsigned char*)json, alloc_size, &written);
    596 	assert(ndb_process_events(ndb, json, written));
    597 
    598 	ndb_destroy(ndb);
    599 
    600 	assert(ndb_init(&ndb, test_dir, &config));
    601 
    602 	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 };
    603 
    604 	struct ndb_txn txn;
    605 	assert(ndb_begin_query(ndb, &txn));
    606 	struct ndb_note *note = ndb_get_note_by_id(&txn, id, &len, NULL);
    607 	assert(note != NULL);
    608 	assert(ndb_note_created_at(note) == 1650054135);
    609 	
    610 	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 };
    611 
    612 	unsigned char profile_note_id[32] = {
    613 		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
    614 	};
    615 
    616 	void *root = ndb_get_profile_by_pubkey(&txn, pk, &len, NULL);
    617 
    618 	assert(root);
    619 	int res = NdbProfileRecord_verify_as_root(root, len);
    620 	printf("NdbProfileRecord verify result %d\n", res);
    621 	assert(res == 0);
    622 
    623 	NdbProfileRecord_table_t profile_record = NdbProfileRecord_as_root(root);
    624 	NdbProfile_table_t profile = NdbProfileRecord_profile_get(profile_record);
    625 	const char *lnurl = NdbProfileRecord_lnurl_get(profile_record);
    626 	const char *name = NdbProfile_name_get(profile);
    627 	uint64_t key = NdbProfileRecord_note_key_get(profile_record);
    628 	assert(name);
    629 	assert(lnurl);
    630 	assert(!strcmp(name, "jb55"));
    631 	assert(!strcmp(lnurl, "fixme"));
    632 
    633 	printf("note_key %" PRIu64 "\n", key);
    634 
    635 	struct ndb_note *n = ndb_get_note_by_key(&txn, key, NULL);
    636 	ndb_end_query(&txn);
    637 	assert(memcmp(profile_note_id, ndb_note_id(n), 32) == 0);
    638 
    639 	//fwrite(profile, len, 1, stdout);
    640 
    641 	ndb_destroy(ndb);
    642 
    643 	free(json);
    644 	free(buf);
    645 }
    646 
    647 static void test_parse_contact_event()
    648 {
    649 	int written;
    650 	static const int alloc_size = 2 << 18;
    651 	char *json = malloc(alloc_size);
    652 	unsigned char *buf = malloc(alloc_size);
    653 	struct ndb_tce tce;
    654 
    655 	assert(read_file("testdata/contacts-event.json", (unsigned char*)json,
    656 			 alloc_size, &written));
    657 	assert(ndb_ws_event_from_json(json, written, &tce, buf, alloc_size, NULL));
    658 
    659 	assert(tce.evtype == NDB_TCE_EVENT);
    660 
    661 	free(json);
    662 	free(buf);
    663 }
    664 
    665 static void test_content_len()
    666 {
    667 	int written;
    668 	static const int alloc_size = 2 << 18;
    669 	char *json = malloc(alloc_size);
    670 	unsigned char *buf = malloc(alloc_size);
    671 	struct ndb_tce tce;
    672 
    673 	assert(read_file("testdata/failed_size.json", (unsigned char*)json,
    674 			 alloc_size, &written));
    675 	assert(ndb_ws_event_from_json(json, written, &tce, buf, alloc_size, NULL));
    676 
    677 	assert(tce.evtype == NDB_TCE_EVENT);
    678 	assert(ndb_note_content_length(tce.event.note) == 0);
    679 
    680 	free(json);
    681 	free(buf);
    682 }
    683 
    684 static void test_parse_json() {
    685 	char hex_id[32] = {0};
    686 	unsigned char buffer[1024];
    687 	struct ndb_note *note;
    688 #define HEX_ID "5004a081e397c6da9dc2f2d6b3134006a9d0e8c1b46689d9fe150bb2f21a204d"
    689 #define HEX_PK "b169f596968917a1abeb4234d3cf3aa9baee2112e58998d17c6db416ad33fe40"
    690 	static const char *json = 
    691 		"{\"id\": \"" HEX_ID "\",\"pubkey\": \"" HEX_PK "\",\"created_at\": 1689836342,\"kind\": 1,\"tags\": [[\"p\",\"" HEX_ID "\"], [\"word\", \"words\", \"w\"]],\"content\": \"共通語\",\"sig\": \"e4d528651311d567f461d7be916c37cbf2b4d530e672f29f15f353291ed6df60c665928e67d2f18861c5ca88\"}";
    692 	int ok;
    693 
    694 	ok = ndb_note_from_json(json, strlen(json), &note, buffer, sizeof(buffer));
    695 	assert(ok);
    696 
    697 	const char *content = ndb_note_content(note);
    698 	unsigned char *id = ndb_note_id(note);
    699 
    700 	hex_decode(HEX_ID, 64, hex_id, sizeof(hex_id));
    701 
    702 	assert(!strcmp(content, "共通語"));
    703 	assert(!memcmp(id, hex_id, 32));
    704 
    705 	assert(ndb_tags_count(ndb_note_tags(note)) == 2);
    706 
    707 	struct ndb_iterator iter, *it = &iter;
    708 	ndb_tags_iterate_start(note, it); assert(ok);
    709 	ok = ndb_tags_iterate_next(it); assert(ok);
    710 	assert(ndb_tag_count(it->tag) == 2);
    711 	assert(!strcmp(ndb_iter_tag_str(it, 0).str, "p"));
    712 	assert(!memcmp(ndb_iter_tag_str(it, 1).id, hex_id, 32));
    713 
    714 	ok = ndb_tags_iterate_next(it); assert(ok);
    715 	assert(ndb_tag_count(it->tag) == 3);
    716 	assert(!strcmp(ndb_iter_tag_str(it, 0).str, "word"));
    717 	assert(!strcmp(ndb_iter_tag_str(it, 1).str, "words"));
    718 	assert(!strcmp(ndb_iter_tag_str(it, 2).str, "w"));
    719 }
    720 
    721 static void test_strings_work_before_finalization() {
    722 	struct ndb_builder builder, *b = &builder;
    723 	struct ndb_note *note;
    724 	int ok;
    725 	unsigned char buf[1024];
    726 
    727 	ok = ndb_builder_init(b, buf, sizeof(buf)); assert(ok);
    728 	ndb_builder_set_content(b, "hello", 5);
    729 
    730 	assert(!strcmp(ndb_note_content(b->note), "hello"));
    731 	assert(ndb_builder_finalize(b, &note, NULL));
    732 
    733 	assert(!strcmp(ndb_note_content(note), "hello"));
    734 }
    735 
    736 static void test_tce_eose() {
    737 	unsigned char buf[1024];
    738 	const char json[] = "[\"EOSE\",\"s\"]";
    739 	struct ndb_tce tce;
    740 	int ok;
    741 
    742 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
    743 	assert(ok);
    744 
    745 	assert(tce.evtype == NDB_TCE_EOSE);
    746 	assert(tce.subid_len == 1);
    747 	assert(!memcmp(tce.subid, "s", 1));
    748 }
    749 
    750 static void test_tce_command_result() {
    751 	unsigned char buf[1024];
    752 	const char json[] = "[\"OK\",\"\",true,\"blocked: ok\"]";
    753 	struct ndb_tce tce;
    754 	int ok;
    755 
    756 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
    757 	assert(ok);
    758 
    759 	assert(tce.evtype == NDB_TCE_OK);
    760 	assert(tce.subid_len == 0);
    761 	assert(tce.command_result.ok == 1);
    762 	assert(!memcmp(tce.subid, "", 0));
    763 }
    764 
    765 static void test_tce_command_result_empty_msg() {
    766 	unsigned char buf[1024];
    767 	const char json[] = "[\"OK\",\"b1d8f68d39c07ce5c5ea10c235100d529b2ed2250140b36a35d940b712dc6eff\",true,\"\"]";
    768 	struct ndb_tce tce;
    769 	int ok;
    770 
    771 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
    772 	assert(ok);
    773 
    774 	assert(tce.evtype == NDB_TCE_OK);
    775 	assert(tce.subid_len == 64);
    776 	assert(tce.command_result.ok == 1);
    777 	assert(tce.command_result.msglen == 0);
    778 	assert(!memcmp(tce.subid, "b1d8f68d39c07ce5c5ea10c235100d529b2ed2250140b36a35d940b712dc6eff", 0));
    779 }
    780 
    781 // test to-client event
    782 static void test_tce() {
    783 
    784 #define HEX_ID "5004a081e397c6da9dc2f2d6b3134006a9d0e8c1b46689d9fe150bb2f21a204d"
    785 #define HEX_PK "b169f596968917a1abeb4234d3cf3aa9baee2112e58998d17c6db416ad33fe40"
    786 #define JSON "{\"id\": \"" HEX_ID "\",\"pubkey\": \"" HEX_PK "\",\"created_at\": 1689836342,\"kind\": 1,\"tags\": [[\"p\",\"" HEX_ID "\"], [\"word\", \"words\", \"w\"]],\"content\": \"共通語\",\"sig\": \"e4d528651311d567f461d7be916c37cbf2b4d530e672f29f15f353291ed6df60c665928e67d2f18861c5ca88\"}"
    787 	unsigned char buf[1024];
    788 	const char json[] = "[\"EVENT\",\"subid123\"," JSON "]";
    789 	struct ndb_tce tce;
    790 	int ok;
    791 
    792 	ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf), NULL);
    793 	assert(ok);
    794 
    795 	assert(tce.evtype == NDB_TCE_EVENT);
    796 	assert(tce.subid_len == 8);
    797 	assert(!memcmp(tce.subid, "subid123", 8));
    798 
    799 #undef HEX_ID
    800 #undef HEX_PK
    801 #undef JSON
    802 }
    803 
    804 #define TEST_BUF_SIZE 10  // For simplicity
    805 
    806 static void test_queue_init_pop_push() {
    807 	struct prot_queue q;
    808 	int buffer[TEST_BUF_SIZE];
    809 	int data;
    810 
    811 	// Initialize
    812 	assert(prot_queue_init(&q, buffer, sizeof(buffer), sizeof(int)) == 1);
    813 
    814 	// Push and Pop
    815 	data = 5;
    816 	assert(prot_queue_push(&q, &data) == 1);
    817 	prot_queue_pop(&q, &data);
    818 	assert(data == 5);
    819 
    820 	// Push to full, and then fail to push
    821 	for (int i = 0; i < TEST_BUF_SIZE; i++) {
    822 		assert(prot_queue_push(&q, &i) == 1);
    823 	}
    824 	assert(prot_queue_push(&q, &data) == 0);  // Should fail as queue is full
    825 
    826 	// Pop to empty, and then fail to pop
    827 	for (int i = 0; i < TEST_BUF_SIZE; i++) {
    828 		assert(prot_queue_try_pop(&q, &data) == 1);
    829 		assert(data == i);
    830 	}
    831 	assert(prot_queue_try_pop(&q, &data) == 0);  // Should fail as queue is empty
    832 }
    833 
    834 // This function will be used by threads to test thread safety.
    835 void* thread_func(void* arg) {
    836 	struct prot_queue* q = (struct prot_queue*) arg;
    837 	int data;
    838 
    839 	for (int i = 0; i < 100; i++) {
    840 		data = i;
    841 		prot_queue_push(q, &data);
    842 		prot_queue_pop(q, &data);
    843 	}
    844 	return NULL;
    845 }
    846 
    847 static void test_queue_thread_safety() {
    848 	struct prot_queue q;
    849 	int buffer[TEST_BUF_SIZE];
    850 	pthread_t threads[2];
    851 
    852 	assert(prot_queue_init(&q, buffer, sizeof(buffer), sizeof(int)) == 1);
    853 
    854 	// Create threads
    855 	for (int i = 0; i < 2; i++) {
    856 		pthread_create(&threads[i], NULL, thread_func, &q);
    857 	}
    858 
    859 	// Join threads
    860 	for (int i = 0; i < 2; i++) {
    861 		pthread_join(threads[i], NULL);
    862 	}
    863 
    864 	// After all operations, the queue should be empty
    865 	int data;
    866 	assert(prot_queue_try_pop(&q, &data) == 0);
    867 }
    868 
    869 static void test_queue_boundary_conditions() {
    870     struct prot_queue q;
    871     int buffer[TEST_BUF_SIZE];
    872     int data;
    873 
    874     // Initialize
    875     assert(prot_queue_init(&q, buffer, sizeof(buffer), sizeof(int)) == 1);
    876 
    877     // Push to full
    878     for (int i = 0; i < TEST_BUF_SIZE; i++) {
    879         assert(prot_queue_push(&q, &i) == 1);
    880     }
    881 
    882     // Try to push to a full queue
    883     int old_head = q.head;
    884     int old_tail = q.tail;
    885     int old_count = q.count;
    886     assert(prot_queue_push(&q, &data) == 0);
    887     
    888     // Assert the queue's state has not changed
    889     assert(old_head == q.head);
    890     assert(old_tail == q.tail);
    891     assert(old_count == q.count);
    892 
    893     // Pop to empty
    894     for (int i = 0; i < TEST_BUF_SIZE; i++) {
    895         assert(prot_queue_try_pop(&q, &data) == 1);
    896     }
    897 
    898     // Try to pop from an empty queue
    899     old_head = q.head;
    900     old_tail = q.tail;
    901     old_count = q.count;
    902     assert(prot_queue_try_pop(&q, &data) == 0);
    903     
    904     // Assert the queue's state has not changed
    905     assert(old_head == q.head);
    906     assert(old_tail == q.tail);
    907     assert(old_count == q.count);
    908 }
    909 
    910 static void test_fast_strchr()
    911 {
    912 	// Test 1: Basic test
    913 	const char *testStr1 = "Hello, World!";
    914 	assert(fast_strchr(testStr1, 'W', strlen(testStr1)) == testStr1 + 7);
    915 
    916 	// Test 2: Character not present in the string
    917 	assert(fast_strchr(testStr1, 'X', strlen(testStr1)) == NULL);
    918 
    919 	// Test 3: Multiple occurrences of the character
    920 	const char *testStr2 = "Multiple occurrences.";
    921 	assert(fast_strchr(testStr2, 'u', strlen(testStr2)) == testStr2 + 1);
    922 
    923 	// Test 4: Check with an empty string
    924 	const char *testStr3 = "";
    925 	assert(fast_strchr(testStr3, 'a', strlen(testStr3)) == NULL);
    926 
    927 	// Test 5: Check with a one-character string
    928 	const char *testStr4 = "a";
    929 	assert(fast_strchr(testStr4, 'a', strlen(testStr4)) == testStr4);
    930 
    931 	// Test 6: Check the last character in the string
    932 	const char *testStr5 = "Last character check";
    933 	assert(fast_strchr(testStr5, 'k', strlen(testStr5)) == testStr5 + 19);
    934 
    935 	// Test 7: Large string test (>16 bytes)
    936 	char *testStr6 = "This is a test for large strings with more than 16 bytes.";
    937 	assert(fast_strchr(testStr6, 'm', strlen(testStr6)) == testStr6 + 38);
    938 }
    939 
    940 static void test_fulltext()
    941 {
    942 	struct ndb *ndb;
    943 	struct ndb_txn txn;
    944 	int written;
    945 	static const int alloc_size = 2 << 18;
    946 	char *json = malloc(alloc_size);
    947 	struct ndb_text_search_results results;
    948 	struct ndb_config config;
    949 	struct ndb_text_search_config search_config;
    950 	ndb_default_config(&config);
    951 	ndb_default_text_search_config(&search_config);
    952 
    953 	assert(ndb_init(&ndb, test_dir, &config));
    954 
    955 	read_file("testdata/search.json", (unsigned char*)json, alloc_size, &written);
    956 	assert(ndb_process_client_events(ndb, json, written));
    957 	ndb_destroy(ndb);
    958 	assert(ndb_init(&ndb, test_dir, &config));
    959 
    960 	ndb_begin_query(ndb, &txn);
    961 	ndb_text_search(&txn, "Jump Over", &results, &search_config);
    962 	ndb_end_query(&txn);
    963 
    964 	ndb_destroy(ndb);
    965 
    966 	free(json);
    967 }
    968 
    969 int main(int argc, const char *argv[]) {
    970 	test_filters();
    971 	test_migrate();
    972 	test_fetched_at();
    973 	test_profile_updates();
    974 	test_reaction_counter();
    975 	test_load_profiles();
    976 	test_basic_event();
    977 	test_empty_tags();
    978 	test_parse_json();
    979 	test_parse_contact_list();
    980 	test_strings_work_before_finalization();
    981 	test_tce();
    982 	test_tce_command_result();
    983 	test_tce_eose();
    984 	test_tce_command_result_empty_msg();
    985 	test_content_len();
    986 	test_fuzz_events();
    987 
    988 	// note fetching
    989 	test_fetch_last_noteid();
    990 
    991 	// fulltext
    992 	test_fulltext();
    993 
    994 	// protected queue tests
    995 	test_queue_init_pop_push();
    996 	test_queue_thread_safety();
    997 	test_queue_boundary_conditions();
    998 
    999 	// memchr stuff
   1000 	test_fast_strchr();
   1001 
   1002 	// profiles
   1003 	test_replacement();
   1004 
   1005 	printf("All tests passed!\n");       // Print this if all tests pass.
   1006 }
   1007 
   1008 
   1009