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