nostrdb.c (231717B)
1 2 #include "nostrdb.h" 3 #include "jsmn.h" 4 #include "hex.h" 5 #include "cursor.h" 6 #include "random.h" 7 #include "ccan/crypto/sha256/sha256.h" 8 #include "bolt11/bolt11.h" 9 #include "bolt11/amount.h" 10 #include "lmdb.h" 11 #include "metadata.h" 12 #include "util.h" 13 #include "cpu.h" 14 #include "block.h" 15 #include "threadpool.h" 16 #include "thread.h" 17 #include "protected_queue.h" 18 #include "memchr.h" 19 #include "print_util.h" 20 #include <stdlib.h> 21 #include <limits.h> 22 #include <assert.h> 23 24 #include "bindings/c/profile_json_parser.h" 25 #include "bindings/c/profile_builder.h" 26 #include "bindings/c/meta_builder.h" 27 #include "bindings/c/meta_reader.h" 28 #include "bindings/c/profile_verifier.h" 29 #include "secp256k1.h" 30 #include "secp256k1_ecdh.h" 31 #include "secp256k1_schnorrsig.h" 32 33 #define max(a,b) ((a) > (b) ? (a) : (b)) 34 #define min(a,b) ((a) < (b) ? (a) : (b)) 35 36 // the maximum number of things threads pop and push in bulk 37 #define THREAD_QUEUE_BATCH 4096 38 39 // maximum number of active subscriptions 40 #define MAX_SUBSCRIPTIONS 256 41 #define MAX_SCAN_CURSORS 12 42 #define MAX_FILTERS 16 43 44 // the maximum size of inbox queues 45 static const int DEFAULT_QUEUE_SIZE = 32768; 46 47 // 2mb scratch size for the writer thread 48 static const int DEFAULT_WRITER_SCRATCH_SIZE = 2097152; 49 50 // increase if we need bigger filters 51 #define NDB_FILTER_PAGES 64 52 53 #define ndb_flag_set(flags, f) ((flags & f) == f) 54 55 #define NDB_PARSED_ID (1 << 0) 56 #define NDB_PARSED_PUBKEY (1 << 1) 57 #define NDB_PARSED_SIG (1 << 2) 58 #define NDB_PARSED_CREATED_AT (1 << 3) 59 #define NDB_PARSED_KIND (1 << 4) 60 #define NDB_PARSED_CONTENT (1 << 5) 61 #define NDB_PARSED_TAGS (1 << 6) 62 #define NDB_PARSED_ALL (NDB_PARSED_ID|NDB_PARSED_PUBKEY|NDB_PARSED_SIG|NDB_PARSED_CREATED_AT|NDB_PARSED_KIND|NDB_PARSED_CONTENT|NDB_PARSED_TAGS) 63 64 typedef int (*ndb_migrate_fn)(struct ndb_txn *); 65 typedef int (*ndb_word_parser_fn)(void *, const char *word, int word_len, 66 int word_index); 67 68 /* parsed nip10 reply data */ 69 struct ndb_note_reply { 70 unsigned char *root; 71 unsigned char *reply; 72 unsigned char *mention; 73 }; 74 75 // these must be byte-aligned, they are directly accessing the serialized data 76 // representation 77 #pragma pack(push, 1) 78 79 union ndb_packed_str { 80 struct { 81 char str[3]; 82 // we assume little endian everywhere. sorry not sorry. 83 unsigned char flag; // NDB_PACKED_STR, etc 84 } packed; 85 86 uint32_t offset; 87 unsigned char bytes[4]; 88 }; 89 90 struct ndb_tag { 91 uint16_t count; 92 union ndb_packed_str strs[0]; 93 }; 94 95 struct ndb_tags { 96 uint16_t padding; 97 uint16_t count; 98 }; 99 100 // v1 101 struct ndb_note { 102 unsigned char version; // v=1 103 unsigned char padding[3]; // keep things aligned 104 unsigned char id[32]; 105 unsigned char pubkey[32]; 106 unsigned char sig[64]; 107 108 uint64_t created_at; 109 uint32_t kind; 110 uint32_t content_length; 111 union ndb_packed_str content; 112 uint32_t strings; 113 // nothing can come after tags since it contains variadic data 114 struct ndb_tags tags; 115 }; 116 117 #pragma pack(pop) 118 119 120 struct ndb_migration { 121 ndb_migrate_fn fn; 122 }; 123 124 struct ndb_profile_record_builder { 125 flatcc_builder_t *builder; 126 void *flatbuf; 127 }; 128 129 // controls whether to continue or stop the json parser 130 enum ndb_idres { 131 NDB_IDRES_CONT, 132 NDB_IDRES_STOP, 133 }; 134 135 // closure data for the id-detecting ingest controller 136 struct ndb_ingest_controller 137 { 138 MDB_txn *read_txn; 139 struct ndb_lmdb *lmdb; 140 struct ndb_note *note; 141 uint64_t note_key; 142 }; 143 144 enum ndb_writer_msgtype { 145 NDB_WRITER_QUIT, // kill thread immediately 146 NDB_WRITER_NOTE, // write a note to the db 147 NDB_WRITER_PROFILE, // write a profile to the db 148 NDB_WRITER_DBMETA, // write ndb metadata 149 NDB_WRITER_PROFILE_LAST_FETCH, // when profiles were last fetched 150 NDB_WRITER_BLOCKS, // write parsed note blocks 151 NDB_WRITER_MIGRATE, // migrate the database 152 NDB_WRITER_NOTE_RELAY, // we already have the note, but we have more relays to write 153 NDB_WRITER_NOTE_META, // write note metadata to the db 154 }; 155 156 // keys used for storing data in the NDB metadata database (NDB_DB_NDB_META) 157 enum ndb_meta_key { 158 NDB_META_KEY_VERSION = 1 159 }; 160 161 struct ndb_json_parser { 162 const char *json; 163 int json_len; 164 struct ndb_builder builder; 165 jsmn_parser json_parser; 166 jsmntok_t *toks, *toks_end; 167 int i; 168 int num_tokens; 169 }; 170 171 // useful to pass to threads on its own 172 struct ndb_lmdb { 173 MDB_env *env; 174 MDB_dbi dbs[NDB_DBS]; 175 }; 176 177 struct ndb_writer { 178 struct ndb_lmdb *lmdb; 179 struct ndb_monitor *monitor; 180 181 int scratch_size; 182 uint32_t ndb_flags; 183 void *queue_buf; 184 int queue_buflen; 185 pthread_t thread_id; 186 187 struct prot_queue inbox; 188 }; 189 190 struct ndb_ingester { 191 struct ndb_lmdb *lmdb; 192 uint32_t flags; 193 struct threadpool tp; 194 struct prot_queue *writer_inbox; 195 void *filter_context; 196 ndb_ingest_filter_fn filter; 197 198 int scratch_size; 199 }; 200 201 struct ndb_filter_group { 202 struct ndb_filter filters[MAX_FILTERS]; 203 int num_filters; 204 }; 205 206 struct ndb_subscription { 207 uint64_t subid; 208 struct ndb_filter_group group; 209 struct prot_queue inbox; 210 }; 211 212 struct ndb_monitor { 213 struct ndb_subscription subscriptions[MAX_SUBSCRIPTIONS]; 214 ndb_sub_fn sub_cb; 215 void *sub_cb_ctx; 216 int num_subscriptions; 217 218 // monitor isn't a full inbox. We want pollers to be able to poll 219 // subscriptions efficiently without going through a message queue, so 220 // we use a simple mutex here. 221 pthread_mutex_t mutex; 222 }; 223 224 struct ndb { 225 struct ndb_lmdb lmdb; 226 struct ndb_ingester ingester; 227 struct ndb_monitor monitor; 228 struct ndb_writer writer; 229 int version; 230 uint32_t flags; // setting flags 231 // lmdb environ handles, etc 232 }; 233 234 /// 235 /// Query Plans 236 /// 237 /// There are general strategies for performing certain types of query 238 /// depending on the filter. For example, for large contact list queries 239 /// with many authors, we simply do a descending scan on created_at 240 /// instead of doing 1000s of pubkey scans. 241 /// 242 /// Query plans are calculated from filters via `ndb_filter_plan` 243 /// 244 enum ndb_query_plan { 245 NDB_PLAN_KINDS, 246 NDB_PLAN_IDS, 247 NDB_PLAN_AUTHORS, 248 NDB_PLAN_AUTHOR_KINDS, 249 NDB_PLAN_CREATED, 250 NDB_PLAN_TAGS, 251 NDB_PLAN_SEARCH, 252 NDB_PLAN_RELAY_KINDS, 253 NDB_PLAN_PROFILE_SEARCH, 254 }; 255 256 // A id + u64 + timestamp 257 struct ndb_id_u64_ts { 258 unsigned char id[32]; // pubkey, id, etc 259 uint64_t u64; // kind, etc 260 uint64_t timestamp; 261 }; 262 263 // A clustered key with an id and a timestamp 264 struct ndb_tsid { 265 unsigned char id[32]; 266 uint64_t timestamp; 267 }; 268 269 // A u64 + timestamp id. Just using this for kinds at the moment. 270 struct ndb_u64_ts { 271 uint64_t u64; // kind, etc 272 uint64_t timestamp; 273 }; 274 275 struct ndb_word 276 { 277 const char *word; 278 int word_len; 279 }; 280 281 struct ndb_search_words 282 { 283 struct ndb_word words[MAX_TEXT_SEARCH_WORDS]; 284 int num_words; 285 }; 286 287 288 static inline int is_replaceable_kind(uint64_t kind) 289 { 290 return kind == 0 || kind == 3 291 || (10000 <= kind && kind < 20000) 292 || (30000 <= kind && kind < 40000); 293 } 294 295 296 // ndb_text_search_key 297 // 298 // This is compressed when in lmdb: 299 // 300 // note_id: varint 301 // strlen: varint 302 // str: cstr 303 // timestamp: varint 304 // word_index: varint 305 // 306 static int ndb_make_text_search_key(unsigned char *buf, int bufsize, 307 int word_index, int word_len, const char *str, 308 uint64_t timestamp, uint64_t note_id, 309 int *keysize) 310 { 311 struct cursor cur; 312 make_cursor(buf, buf + bufsize, &cur); 313 314 // TODO: need update this to uint64_t 315 // we push this first because our query function can pull this off 316 // quickly to check matches 317 if (!cursor_push_varint(&cur, (int32_t)note_id)) 318 return 0; 319 320 // string length 321 if (!cursor_push_varint(&cur, word_len)) 322 return 0; 323 324 // non-null terminated, lowercase string 325 if (!cursor_push_lowercase(&cur, str, word_len)) 326 return 0; 327 328 // TODO: need update this to uint64_t 329 if (!cursor_push_varint(&cur, (int)timestamp)) 330 return 0; 331 332 // the index of the word in the content so that we can do more accurate 333 // phrase searches 334 if (!cursor_push_varint(&cur, word_index)) 335 return 0; 336 337 // pad to 8-byte alignment 338 if (!cursor_align(&cur, 8)) 339 return 0; 340 341 *keysize = cur.p - cur.start; 342 assert((*keysize % 8) == 0); 343 344 return 1; 345 } 346 347 static int ndb_make_noted_text_search_key(unsigned char *buf, int bufsize, 348 int wordlen, const char *word, 349 uint64_t timestamp, uint64_t note_id, 350 int *keysize) 351 { 352 return ndb_make_text_search_key(buf, bufsize, 0, wordlen, word, 353 timestamp, note_id, keysize); 354 } 355 356 static int ndb_make_text_search_key_low(unsigned char *buf, int bufsize, 357 int wordlen, const char *word, 358 uint64_t since, 359 int *keysize) 360 { 361 uint64_t note_id; 362 note_id = 0; 363 return ndb_make_text_search_key(buf, bufsize, 0, wordlen, word, 364 since, note_id, keysize); 365 } 366 367 static int ndb_make_text_search_key_high(unsigned char *buf, int bufsize, 368 int wordlen, const char *word, 369 uint64_t until, 370 int *keysize) 371 { 372 uint64_t note_id; 373 note_id = INT32_MAX; 374 return ndb_make_text_search_key(buf, bufsize, 0, wordlen, word, 375 until, note_id, keysize); 376 } 377 378 typedef int (*ndb_text_search_key_order_fn)(unsigned char *buf, int bufsize, int wordlen, const char *word, uint64_t timestamp, int *keysize); 379 380 /** From LMDB: Compare two items lexically */ 381 static int mdb_cmp_memn(const MDB_val *a, const MDB_val *b) { 382 int diff; 383 ssize_t len_diff; 384 unsigned int len; 385 386 len = a->mv_size; 387 len_diff = (ssize_t) a->mv_size - (ssize_t) b->mv_size; 388 if (len_diff > 0) { 389 len = b->mv_size; 390 len_diff = 1; 391 } 392 393 diff = memcmp(a->mv_data, b->mv_data, len); 394 return diff ? diff : len_diff<0 ? -1 : len_diff; 395 } 396 397 static int ndb_tag_key_compare(const MDB_val *a, const MDB_val *b) 398 { 399 MDB_val va, vb; 400 uint64_t ts_a, ts_b; 401 int cmp; 402 403 va.mv_data = a->mv_data; 404 va.mv_size = a->mv_size - 8; 405 406 vb.mv_data = b->mv_data; 407 vb.mv_size = b->mv_size - 8; 408 409 if ((cmp = mdb_cmp_memn(&va, &vb))) 410 return cmp; 411 412 ts_a = *(uint64_t*)((unsigned char *)va.mv_data + va.mv_size); 413 ts_b = *(uint64_t*)((unsigned char *)vb.mv_data + vb.mv_size); 414 415 if (ts_a < ts_b) 416 return -1; 417 else if (ts_a > ts_b) 418 return 1; 419 420 return 0; 421 } 422 423 static int ndb_text_search_key_compare(const MDB_val *a, const MDB_val *b) 424 { 425 struct cursor ca, cb; 426 uint64_t sa, sb, nid_a, nid_b; 427 MDB_val a2, b2; 428 429 make_cursor(a->mv_data, (unsigned char *)a->mv_data + a->mv_size, &ca); 430 make_cursor(b->mv_data, (unsigned char *)b->mv_data + b->mv_size, &cb); 431 432 // note_id 433 if (unlikely(!cursor_pull_varint(&ca, &nid_a) || !cursor_pull_varint(&cb, &nid_b))) 434 return 0; 435 436 // string size 437 if (unlikely(!cursor_pull_varint(&ca, &sa) || !cursor_pull_varint(&cb, &sb))) 438 return 0; 439 440 a2.mv_data = ca.p; 441 a2.mv_size = sa; 442 443 b2.mv_data = cb.p; 444 b2.mv_size = sb; 445 446 int cmp = mdb_cmp_memn(&a2, &b2); 447 if (cmp) return cmp; 448 449 // skip over string 450 ca.p += sa; 451 cb.p += sb; 452 453 // timestamp 454 if (unlikely(!cursor_pull_varint(&ca, &sa) || !cursor_pull_varint(&cb, &sb))) 455 return 0; 456 457 if (sa < sb) return -1; 458 else if (sa > sb) return 1; 459 460 // note_id 461 if (nid_a < nid_b) return -1; 462 else if (nid_a > nid_b) return 1; 463 464 // word index 465 if (unlikely(!cursor_pull_varint(&ca, &sa) || !cursor_pull_varint(&cb, &sb))) 466 return 0; 467 468 if (sa < sb) return -1; 469 else if (sa > sb) return 1; 470 471 return 0; 472 } 473 474 static inline int ndb_unpack_text_search_key_noteid( 475 struct cursor *cur, uint64_t *note_id) 476 { 477 if (!cursor_pull_varint(cur, note_id)) 478 return 0; 479 480 return 1; 481 } 482 483 // faster peek of just the string instead of unpacking everything 484 // this is used to quickly discard range query matches if there is no 485 // common prefix 486 static inline int ndb_unpack_text_search_key_string(struct cursor *cur, 487 const char **str, 488 int *str_len) 489 { 490 uint64_t len; 491 492 if (!cursor_pull_varint(cur, &len)) 493 return 0; 494 495 *str_len = len; 496 497 *str = (const char *)cur->p; 498 499 if (!cursor_skip(cur, *str_len)) 500 return 0; 501 502 return 1; 503 } 504 505 // should be called after ndb_unpack_text_search_key_string. It continues 506 // the unpacking of a text search key if we've already started it. 507 static inline int 508 ndb_unpack_remaining_text_search_key(struct cursor *cur, 509 struct ndb_text_search_key *key) 510 { 511 if (!cursor_pull_varint(cur, &key->timestamp)) 512 return 0; 513 514 if (!cursor_pull_varint(cur, &key->word_index)) 515 return 0; 516 517 return 1; 518 } 519 520 // unpack a fulltext search key 521 // 522 // full version of string + unpack remaining. This is split up because text 523 // searching only requires to pull the string for prefix searching, and the 524 // remaining is optional 525 static inline int ndb_unpack_text_search_key(unsigned char *p, int len, 526 struct ndb_text_search_key *key) 527 { 528 struct cursor c; 529 make_cursor(p, p + len, &c); 530 531 if (!ndb_unpack_text_search_key_noteid(&c, &key->note_id)) 532 return 0; 533 534 if (!ndb_unpack_text_search_key_string(&c, &key->str, &key->str_len)) 535 return 0; 536 537 return ndb_unpack_remaining_text_search_key(&c, key); 538 } 539 540 // Copies only lowercase characters to the destination string and fills the rest with null bytes. 541 // `dst` and `src` are pointers to the destination and source strings, respectively. 542 // `n` is the maximum number of characters to copy. 543 static void lowercase_strncpy(char *dst, const char *src, int n) { 544 int j = 0, i = 0; 545 546 if (!dst || !src || n == 0) { 547 return; 548 } 549 550 while (src[i] != '\0' && j < n) { 551 dst[j++] = tolower(src[i++]); 552 } 553 554 // Null-terminate and fill the destination string 555 while (j < n) { 556 dst[j++] = '\0'; 557 } 558 } 559 560 static inline int ndb_filter_elem_is_ptr(struct ndb_filter_field *field) { 561 return field->elem_type == NDB_ELEMENT_STRING || field->elem_type == NDB_ELEMENT_ID; 562 } 563 564 // Copy the filter 565 int ndb_filter_clone(struct ndb_filter *dst, struct ndb_filter *src) 566 { 567 size_t src_size, elem_size, data_size; 568 569 memcpy(dst, src, sizeof(*src)); 570 571 elem_size = src->elem_buf.end - src->elem_buf.start; 572 data_size = src->data_buf.end - src->data_buf.start; 573 src_size = data_size + elem_size; 574 575 // let's only allow finalized filters to be cloned 576 if (!src || !src->finalized) 577 return 0; 578 579 dst->elem_buf.start = malloc(src_size); 580 dst->elem_buf.end = dst->elem_buf.start + elem_size; 581 dst->elem_buf.p = dst->elem_buf.end; 582 583 dst->data_buf.start = dst->elem_buf.start + elem_size; 584 dst->data_buf.end = dst->data_buf.start + data_size; 585 dst->data_buf.p = dst->data_buf.end; 586 587 if (dst->elem_buf.start == NULL) 588 return 0; 589 590 memcpy(dst->elem_buf.start, src->elem_buf.start, src_size); 591 592 return 1; 593 } 594 595 // "Finalize" the filter. This resizes the allocated heap buffers so that they 596 // are as small as possible. This also prevents new fields from being added 597 int ndb_filter_end(struct ndb_filter *filter) 598 { 599 #ifdef NDB_LOG 600 size_t orig_size; 601 #endif 602 size_t data_len, elem_len; 603 unsigned char *rel; 604 605 assert(filter); 606 assert(filter->elem_buf.start); 607 608 if (filter->finalized == 1) 609 return 0; 610 611 // move the data buffer to the end of the element buffer and update 612 // all of the element pointers accordingly 613 data_len = filter->data_buf.p - filter->data_buf.start; 614 elem_len = filter->elem_buf.p - filter->elem_buf.start; 615 #ifdef NDB_LOG 616 orig_size = filter->data_buf.end - filter->elem_buf.start; 617 #endif 618 619 // cap the elem buff 620 filter->elem_buf.end = filter->elem_buf.p; 621 622 // move the data buffer to the end of the element buffer 623 memmove(filter->elem_buf.p, filter->data_buf.start, data_len); 624 625 // realloc the whole thing 626 rel = realloc(filter->elem_buf.start, elem_len + data_len); 627 if (rel) 628 filter->elem_buf.start = rel; 629 assert(filter->elem_buf.start); 630 filter->elem_buf.end = filter->elem_buf.start + elem_len; 631 filter->elem_buf.p = filter->elem_buf.end; 632 633 filter->data_buf.start = filter->elem_buf.end; 634 filter->data_buf.end = filter->data_buf.start + data_len; 635 filter->data_buf.p = filter->data_buf.end; 636 637 filter->finalized = 1; 638 639 ndb_debug("ndb_filter_end: %ld -> %ld\n", orig_size, elem_len + data_len); 640 641 return 1; 642 } 643 644 static inline struct ndb_filter_elements * 645 ndb_filter_get_elements_by_offset(const struct ndb_filter *filter, int offset) 646 { 647 struct ndb_filter_elements *els; 648 649 if (offset < 0) 650 return NULL; 651 652 els = (struct ndb_filter_elements *)(filter->elem_buf.start + offset); 653 654 if ((unsigned char *)els > filter->elem_buf.p) 655 return NULL; 656 657 return els; 658 } 659 660 struct ndb_filter_elements * 661 ndb_filter_current_element(const struct ndb_filter *filter) 662 { 663 return ndb_filter_get_elements_by_offset(filter, filter->current); 664 } 665 666 struct ndb_filter_elements * 667 ndb_filter_get_elements(const struct ndb_filter *filter, int index) 668 { 669 if (filter->num_elements <= 0) 670 return NULL; 671 672 if (index > filter->num_elements-1) 673 return NULL; 674 675 return ndb_filter_get_elements_by_offset(filter, filter->elements[index]); 676 } 677 678 static inline unsigned char * 679 ndb_filter_elements_data(const struct ndb_filter *filter, int offset) 680 { 681 unsigned char *data; 682 683 if (offset < 0) 684 return NULL; 685 686 data = filter->data_buf.start + offset; 687 if (data > filter->data_buf.p) 688 return NULL; 689 690 return data; 691 } 692 693 struct ndb_filter_custom * 694 ndb_filter_get_custom_element(const struct ndb_filter *filter, const struct ndb_filter_elements *els) 695 { 696 return (struct ndb_filter_custom *)ndb_filter_elements_data(filter, els->elements[0]); 697 } 698 699 unsigned char * 700 ndb_filter_get_id_element(const struct ndb_filter *filter, const struct ndb_filter_elements *els, int index) 701 { 702 return ndb_filter_elements_data(filter, els->elements[index]); 703 } 704 705 const char * 706 ndb_filter_get_string_element(const struct ndb_filter *filter, const struct ndb_filter_elements *els, int index) 707 { 708 return (const char *)ndb_filter_elements_data(filter, els->elements[index]); 709 } 710 711 uint64_t * 712 ndb_filter_get_int_element_ptr(struct ndb_filter_elements *els, int index) 713 { 714 return &els->elements[index]; 715 } 716 717 uint64_t 718 ndb_filter_get_int_element(const struct ndb_filter_elements *els, int index) 719 { 720 return els->elements[index]; 721 } 722 723 int ndb_filter_init_with(struct ndb_filter *filter, int pages) 724 { 725 struct cursor cur; 726 int page_size, elem_size, data_size, buf_size; 727 728 page_size = 4096; // assuming this, not a big deal if we're wrong 729 730 buf_size = page_size * pages; 731 elem_size = buf_size / 4; 732 data_size = buf_size - elem_size; 733 734 unsigned char *buf = malloc(buf_size); 735 if (!buf) 736 return 0; 737 738 // init memory arena for the cursor 739 make_cursor(buf, buf + buf_size, &cur); 740 741 cursor_slice(&cur, &filter->elem_buf, elem_size); 742 cursor_slice(&cur, &filter->data_buf, data_size); 743 744 // make sure we are fully allocated 745 assert(cur.p == cur.end); 746 747 // make sure elem_buf is the start of the buffer 748 assert(filter->elem_buf.start == cur.start); 749 750 filter->num_elements = 0; 751 filter->elements[0] = 0; 752 filter->current = -1; 753 filter->finalized = 0; 754 755 return 1; 756 } 757 758 int ndb_filter_init(struct ndb_filter *filter) { 759 return ndb_filter_init_with(filter, NDB_FILTER_PAGES); 760 } 761 762 void ndb_filter_destroy(struct ndb_filter *filter) 763 { 764 if (filter->elem_buf.start) 765 free(filter->elem_buf.start); 766 767 memset(filter, 0, sizeof(*filter)); 768 } 769 770 static const char *ndb_filter_field_name(enum ndb_filter_fieldtype field) 771 { 772 switch (field) { 773 case NDB_FILTER_IDS: return "ids"; 774 case NDB_FILTER_AUTHORS: return "authors"; 775 case NDB_FILTER_KINDS: return "kinds"; 776 case NDB_FILTER_TAGS: return "tags"; 777 case NDB_FILTER_SINCE: return "since"; 778 case NDB_FILTER_UNTIL: return "until"; 779 case NDB_FILTER_LIMIT: return "limit"; 780 case NDB_FILTER_SEARCH: return "search"; 781 case NDB_FILTER_RELAYS: return "relays"; 782 case NDB_FILTER_CUSTOM: return "custom"; 783 } 784 785 return "unknown"; 786 } 787 788 static int ndb_filter_start_field_impl(struct ndb_filter *filter, enum ndb_filter_fieldtype field, char tag) 789 { 790 int i; 791 struct ndb_filter_elements *els, *el; 792 793 if (ndb_filter_current_element(filter)) { 794 ndb_debug("ndb_filter_start_field: filter field already in progress, did you forget to call ndb_filter_end_field?\n"); 795 return 0; 796 } 797 798 // you can only start and end fields once 799 for (i = 0; i < filter->num_elements; i++) { 800 el = ndb_filter_get_elements(filter, i); 801 assert(el); 802 // TODO: fix this tags check to try to find matching tags 803 if (el->field.type == field && field != NDB_FILTER_TAGS) { 804 fprintf(stderr, "ndb_filter_start_field: field '%s' already exists\n", 805 ndb_filter_field_name(field)); 806 return 0; 807 } 808 } 809 810 filter->current = filter->elem_buf.p - filter->elem_buf.start; 811 els = ndb_filter_current_element(filter); 812 assert(els); 813 814 // advance elem buffer to the variable data section 815 if (!cursor_skip(&filter->elem_buf, sizeof(struct ndb_filter_elements))) { 816 fprintf(stderr, "ndb_filter_start_field: '%s' oom (todo: realloc?)\n", 817 ndb_filter_field_name(field)); 818 return 0; 819 } 820 821 els->field.type = field; 822 els->field.tag = tag; 823 els->field.elem_type = 0; 824 els->count = 0; 825 826 return 1; 827 } 828 829 int ndb_filter_start_field(struct ndb_filter *filter, enum ndb_filter_fieldtype field) 830 { 831 return ndb_filter_start_field_impl(filter, field, 0); 832 } 833 834 int ndb_filter_start_tag_field(struct ndb_filter *filter, char tag) 835 { 836 return ndb_filter_start_field_impl(filter, NDB_FILTER_TAGS, tag); 837 } 838 839 static int ndb_filter_add_element(struct ndb_filter *filter, union ndb_filter_element el) 840 { 841 struct ndb_filter_elements *current; 842 uint64_t offset; 843 844 if (!(current = ndb_filter_current_element(filter))) 845 return 0; 846 847 offset = filter->data_buf.p - filter->data_buf.start; 848 849 switch (current->field.type) { 850 case NDB_FILTER_CUSTOM: 851 if (!cursor_push(&filter->data_buf, (unsigned char *)&el, sizeof(el))) 852 return 0; 853 break; 854 case NDB_FILTER_IDS: 855 case NDB_FILTER_AUTHORS: 856 if (!cursor_push(&filter->data_buf, (unsigned char *)el.id, 32)) 857 return 0; 858 break; 859 case NDB_FILTER_KINDS: 860 offset = el.integer; 861 break; 862 case NDB_FILTER_SINCE: 863 case NDB_FILTER_UNTIL: 864 case NDB_FILTER_LIMIT: 865 // only one allowed for since/until 866 if (current->count != 0) 867 return 0; 868 offset = el.integer; 869 break; 870 case NDB_FILTER_SEARCH: 871 if (current->field.elem_type != NDB_ELEMENT_STRING) { 872 return 0; 873 } 874 if (!cursor_push(&filter->data_buf, (unsigned char *)el.string.string, el.string.len)) 875 return 0; 876 if (!cursor_push_byte(&filter->data_buf, 0)) 877 return 0; 878 break; 879 case NDB_FILTER_TAGS: 880 switch (current->field.elem_type) { 881 case NDB_ELEMENT_ID: 882 if (!cursor_push(&filter->data_buf, (unsigned char *)el.id, 32)) 883 return 0; 884 break; 885 case NDB_ELEMENT_STRING: 886 if (!cursor_push(&filter->data_buf, (unsigned char *)el.string.string, el.string.len)) 887 return 0; 888 if (!cursor_push_byte(&filter->data_buf, 0)) 889 return 0; 890 break; 891 case NDB_ELEMENT_INT: 892 // ints are not allowed in tag filters 893 case NDB_ELEMENT_UNKNOWN: 894 case NDB_ELEMENT_CUSTOM: 895 return 0; 896 } 897 // push a pointer of the string in the databuf as an element 898 break; 899 case NDB_FILTER_RELAYS: 900 if (current->field.elem_type != NDB_ELEMENT_STRING) { 901 return 0; 902 } 903 if (!cursor_push(&filter->data_buf, (unsigned char *)el.string.string, el.string.len)) 904 return 0; 905 if (!cursor_push_byte(&filter->data_buf, 0)) 906 return 0; 907 break; 908 } 909 910 if (!cursor_push(&filter->elem_buf, (unsigned char *)&offset, 911 sizeof(offset))) { 912 return 0; 913 } 914 915 current->count++; 916 917 return 1; 918 } 919 920 static int ndb_filter_set_elem_type(struct ndb_filter *filter, 921 enum ndb_generic_element_type elem_type) 922 { 923 enum ndb_generic_element_type current_elem_type; 924 struct ndb_filter_elements *current; 925 926 if (!(current = ndb_filter_current_element(filter))) 927 return 0; 928 929 current_elem_type = current->field.elem_type; 930 931 // element types must be uniform 932 if (current_elem_type != elem_type && current_elem_type != NDB_ELEMENT_UNKNOWN) { 933 fprintf(stderr, "ndb_filter_set_elem_type: element types must be uniform\n"); 934 return 0; 935 } 936 937 current->field.elem_type = elem_type; 938 939 return 1; 940 } 941 942 943 int ndb_filter_add_str_element_len(struct ndb_filter *filter, const char *str, int len) 944 { 945 union ndb_filter_element el; 946 struct ndb_filter_elements *current; 947 948 if (!(current = ndb_filter_current_element(filter))) 949 return 0; 950 951 // only generic tags and search queries are allowed to have strings 952 switch (current->field.type) { 953 case NDB_FILTER_SINCE: 954 case NDB_FILTER_UNTIL: 955 case NDB_FILTER_LIMIT: 956 case NDB_FILTER_IDS: 957 case NDB_FILTER_AUTHORS: 958 case NDB_FILTER_KINDS: 959 case NDB_FILTER_CUSTOM: 960 return 0; 961 case NDB_FILTER_SEARCH: 962 if (current->count == 1) { 963 // you can't add more than one string to a search 964 return 0; 965 } 966 break; 967 case NDB_FILTER_RELAYS: 968 case NDB_FILTER_TAGS: 969 break; 970 } 971 972 if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_STRING)) 973 return 0; 974 975 el.string.string = str; 976 el.string.len = len; 977 978 return ndb_filter_add_element(filter, el); 979 } 980 981 int ndb_filter_add_str_element(struct ndb_filter *filter, const char *str) 982 { 983 return ndb_filter_add_str_element_len(filter, str, strlen(str)); 984 } 985 986 int ndb_filter_add_custom_filter_element(struct ndb_filter *filter, ndb_filter_callback_fn *cb, void *ctx) 987 { 988 union ndb_filter_element el; 989 struct ndb_filter_elements *current; 990 struct ndb_filter_custom custom; 991 992 custom.cb = cb; 993 custom.ctx = ctx; 994 995 if (!(current = ndb_filter_current_element(filter))) 996 return 0; 997 998 switch (current->field.type) { 999 case NDB_FILTER_CUSTOM: 1000 break; 1001 case NDB_FILTER_IDS: 1002 case NDB_FILTER_AUTHORS: 1003 case NDB_FILTER_TAGS: 1004 case NDB_FILTER_SEARCH: 1005 case NDB_FILTER_RELAYS: 1006 case NDB_FILTER_KINDS: 1007 case NDB_FILTER_SINCE: 1008 case NDB_FILTER_UNTIL: 1009 case NDB_FILTER_LIMIT: 1010 return 0; 1011 } 1012 1013 if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_CUSTOM)) 1014 return 0; 1015 1016 el.custom_filter = custom; 1017 1018 return ndb_filter_add_element(filter, el); 1019 } 1020 1021 int ndb_filter_add_int_element(struct ndb_filter *filter, uint64_t integer) 1022 { 1023 union ndb_filter_element el; 1024 struct ndb_filter_elements *current; 1025 if (!(current = ndb_filter_current_element(filter))) 1026 return 0; 1027 1028 switch (current->field.type) { 1029 case NDB_FILTER_IDS: 1030 case NDB_FILTER_AUTHORS: 1031 case NDB_FILTER_TAGS: 1032 case NDB_FILTER_SEARCH: 1033 case NDB_FILTER_RELAYS: 1034 case NDB_FILTER_CUSTOM: 1035 return 0; 1036 case NDB_FILTER_KINDS: 1037 case NDB_FILTER_SINCE: 1038 case NDB_FILTER_UNTIL: 1039 case NDB_FILTER_LIMIT: 1040 break; 1041 } 1042 1043 if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_INT)) 1044 return 0; 1045 1046 el.integer = integer; 1047 1048 return ndb_filter_add_element(filter, el); 1049 } 1050 1051 int ndb_filter_add_id_element(struct ndb_filter *filter, const unsigned char *id) 1052 { 1053 union ndb_filter_element el; 1054 struct ndb_filter_elements *current; 1055 1056 if (!(current = ndb_filter_current_element(filter))) 1057 return 0; 1058 1059 // only certain filter types allow pushing id elements 1060 switch (current->field.type) { 1061 case NDB_FILTER_SINCE: 1062 case NDB_FILTER_UNTIL: 1063 case NDB_FILTER_LIMIT: 1064 case NDB_FILTER_KINDS: 1065 case NDB_FILTER_SEARCH: 1066 case NDB_FILTER_RELAYS: 1067 case NDB_FILTER_CUSTOM: 1068 return 0; 1069 case NDB_FILTER_IDS: 1070 case NDB_FILTER_AUTHORS: 1071 case NDB_FILTER_TAGS: 1072 break; 1073 } 1074 1075 if (!ndb_filter_set_elem_type(filter, NDB_ELEMENT_ID)) 1076 return 0; 1077 1078 // this is needed so that generic filters know its an id 1079 el.id = id; 1080 1081 return ndb_filter_add_element(filter, el); 1082 } 1083 1084 static int ndb_tag_filter_matches(struct ndb_filter *filter, 1085 struct ndb_filter_elements *els, 1086 struct ndb_note *note) 1087 { 1088 int i; 1089 const unsigned char *id; 1090 const char *el_str; 1091 struct ndb_iterator iter, *it = &iter; 1092 struct ndb_str str; 1093 1094 ndb_tags_iterate_start(note, it); 1095 1096 while (ndb_tags_iterate_next(it)) { 1097 // we're looking for tags with 2 or more entries: ["p", id], etc 1098 if (it->tag->count < 2) 1099 continue; 1100 1101 str = ndb_tag_str(note, it->tag, 0); 1102 1103 // we only care about packed strings (single char, etc) 1104 if (str.flag != NDB_PACKED_STR) 1105 continue; 1106 1107 // do we have #e matching e (or p, etc) 1108 if (str.str[0] != els->field.tag || str.str[1] != 0) 1109 continue; 1110 1111 str = ndb_tag_str(note, it->tag, 1); 1112 1113 switch (els->field.elem_type) { 1114 case NDB_ELEMENT_ID: 1115 // if our filter element type is an id, then we 1116 // expect a packed id in the tag, otherwise skip 1117 if (str.flag != NDB_PACKED_ID) 1118 continue; 1119 break; 1120 case NDB_ELEMENT_STRING: 1121 // if our filter element type is a string, then 1122 // we should not expect an id 1123 if (str.flag == NDB_PACKED_ID) 1124 continue; 1125 1126 break; 1127 case NDB_ELEMENT_UNKNOWN: 1128 default: 1129 // For some reason the element type is not set. It's 1130 // possible nothing was added to the generic filter? 1131 // Let's just fail here and log a note for debugging 1132 fprintf(stderr, "UNUSUAL ndb_tag_filter_matches: have unknown element type %d\n", els->field.elem_type); 1133 return 0; 1134 } 1135 1136 for (i = 0; i < els->count; i++) { 1137 switch (els->field.elem_type) { 1138 case NDB_ELEMENT_ID: 1139 id = ndb_filter_get_id_element(filter, els, i); 1140 if (!memcmp(id, str.id, 32)) 1141 return 1; 1142 break; 1143 case NDB_ELEMENT_STRING: 1144 el_str = ndb_filter_get_string_element(filter, els, i); 1145 if (!strcmp(el_str, str.str)) 1146 return 1; 1147 break; 1148 case NDB_ELEMENT_INT: 1149 // int elements int tag queries are not supported 1150 case NDB_ELEMENT_UNKNOWN: 1151 case NDB_ELEMENT_CUSTOM: 1152 return 0; 1153 } 1154 } 1155 } 1156 1157 return 0; 1158 } 1159 1160 struct search_id_state { 1161 struct ndb_filter *filter; 1162 struct ndb_filter_elements *els; 1163 unsigned char *key; 1164 }; 1165 1166 static int compare_ids(const void *pa, const void *pb) 1167 { 1168 unsigned char *a = *(unsigned char **)pa; 1169 unsigned char *b = *(unsigned char **)pb; 1170 1171 return memcmp(a, b, 32); 1172 } 1173 1174 static int compare_strs(const void *pa, const void *pb) 1175 { 1176 const char *a = *(const char **)pa; 1177 const char *b = *(const char **)pb; 1178 1179 return strcmp(a, b); 1180 } 1181 1182 static int search_strs(const void *ctx, const void *mstr_ptr) 1183 { 1184 // we reuse search_id_state here and just cast to (const char *) when 1185 // needed 1186 struct search_id_state *state; 1187 const char *mstr_str; 1188 uint32_t mstr; 1189 1190 state = (struct search_id_state *)ctx; 1191 mstr = *(uint32_t *)mstr_ptr; 1192 1193 mstr_str = (const char *)ndb_filter_elements_data(state->filter, mstr); 1194 assert(mstr_str); 1195 1196 return strcmp((const char *)state->key, mstr_str); 1197 } 1198 1199 static int search_ids(const void *ctx, const void *mid_ptr) 1200 { 1201 struct search_id_state *state; 1202 unsigned char *mid_id; 1203 uint32_t mid; 1204 1205 state = (struct search_id_state *)ctx; 1206 mid = *(uint32_t *)mid_ptr; 1207 1208 mid_id = ndb_filter_elements_data(state->filter, mid); 1209 assert(mid_id); 1210 1211 return memcmp(state->key, mid_id, 32); 1212 } 1213 1214 static int compare_kinds(const void *pa, const void *pb) 1215 { 1216 1217 // NOTE: this should match type in `union ndb_filter_element` 1218 uint64_t a = *(uint64_t *)pa; 1219 uint64_t b = *(uint64_t *)pb; 1220 1221 if (a < b) { 1222 return -1; 1223 } else if (a > b) { 1224 return 1; 1225 } else { 1226 return 0; 1227 } 1228 } 1229 1230 // 1231 // returns 1 if a filter matches a note 1232 static int ndb_filter_matches_with(struct ndb_filter *filter, 1233 struct ndb_note *note, int already_matched, 1234 struct ndb_note_relay_iterator *relay_iter) 1235 { 1236 int i, j; 1237 struct ndb_filter_elements *els; 1238 struct search_id_state state; 1239 struct ndb_filter_custom *custom; 1240 1241 state.filter = filter; 1242 1243 for (i = 0; i < filter->num_elements; i++) { 1244 els = ndb_filter_get_elements(filter, i); 1245 state.els = els; 1246 assert(els); 1247 1248 // if we know we already match from a query scan result, 1249 // we can skip this check 1250 if ((1 << els->field.type) & already_matched) 1251 continue; 1252 1253 switch (els->field.type) { 1254 case NDB_FILTER_KINDS: 1255 for (j = 0; j < els->count; j++) { 1256 if ((unsigned int)els->elements[j] == note->kind) 1257 goto cont; 1258 } 1259 break; 1260 case NDB_FILTER_RELAYS: 1261 // for each relay the note was seen on, see if any match 1262 if (!relay_iter) { 1263 assert(!"expected relay iterator..."); 1264 break; 1265 } 1266 while ((state.key = (unsigned char *)ndb_note_relay_iterate_next(relay_iter))) { 1267 // relays in filters are always sorted 1268 if (bsearch(&state, &els->elements[0], els->count, 1269 sizeof(els->elements[0]), search_strs)) { 1270 ndb_note_relay_iterate_close(relay_iter); 1271 goto cont; 1272 } 1273 } 1274 ndb_note_relay_iterate_close(relay_iter); 1275 break; 1276 case NDB_FILTER_IDS: 1277 state.key = ndb_note_id(note); 1278 if (bsearch(&state, &els->elements[0], els->count, 1279 sizeof(els->elements[0]), search_ids)) { 1280 continue; 1281 } 1282 break; 1283 case NDB_FILTER_AUTHORS: 1284 state.key = ndb_note_pubkey(note); 1285 if (bsearch(&state, &els->elements[0], els->count, 1286 sizeof(els->elements[0]), search_ids)) { 1287 continue; 1288 } 1289 break; 1290 case NDB_FILTER_TAGS: 1291 if (ndb_tag_filter_matches(filter, els, note)) 1292 continue; 1293 break; 1294 case NDB_FILTER_SINCE: 1295 assert(els->count == 1); 1296 if (note->created_at >= els->elements[0]) 1297 continue; 1298 break; 1299 case NDB_FILTER_UNTIL: 1300 assert(els->count == 1); 1301 if (note->created_at < els->elements[0]) 1302 continue; 1303 break; 1304 case NDB_FILTER_SEARCH: 1305 // TODO: matching search filters will need an accelerated 1306 // data structure, like our minimal perfect hashmap 1307 // idea for mutewords. 1308 // 1309 // We'll also want to store tokenized words in the filter 1310 // itself, so that we can walk over each word and check 1311 // the hashmap to see if the note contains at least 1312 // one word. 1313 // 1314 // For now we always return true, since we assume 1315 // the search index will be walked for these kinds 1316 // of queries. 1317 continue; 1318 case NDB_FILTER_CUSTOM: 1319 custom = ndb_filter_get_custom_element(filter, els); 1320 if (custom->cb(custom->ctx, note)) 1321 continue; 1322 break; 1323 1324 case NDB_FILTER_LIMIT: 1325 cont: 1326 continue; 1327 } 1328 1329 // all need to match 1330 return 0; 1331 } 1332 1333 return 1; 1334 } 1335 1336 int ndb_filter_matches(struct ndb_filter *filter, struct ndb_note *note) 1337 { 1338 return ndb_filter_matches_with(filter, note, 0, NULL); 1339 } 1340 1341 int ndb_filter_matches_with_relay(struct ndb_filter *filter, 1342 struct ndb_note *note, 1343 struct ndb_note_relay_iterator *note_relay_iter) 1344 { 1345 return ndb_filter_matches_with(filter, note, 0, note_relay_iter); 1346 } 1347 1348 // because elements are stored as offsets and qsort doesn't support context, 1349 // we do a dumb thing where we convert elements to pointers and back if we 1350 // are doing an id or string sort 1351 static void sort_filter_elements(struct ndb_filter *filter, 1352 struct ndb_filter_elements *els, 1353 int (*cmp)(const void *, const void *)) 1354 { 1355 int i; 1356 1357 assert(ndb_filter_elem_is_ptr(&els->field)); 1358 1359 for (i = 0; i < els->count; i++) 1360 els->elements[i] += (uint64_t)filter->data_buf.start; 1361 1362 qsort(&els->elements[0], els->count, sizeof(els->elements[0]), cmp); 1363 1364 for (i = 0; i < els->count; i++) 1365 els->elements[i] -= (uint64_t)filter->data_buf.start; 1366 } 1367 1368 static int ndb_filter_field_eq(struct ndb_filter *a_filt, 1369 struct ndb_filter_elements *a_field, 1370 struct ndb_filter *b_filt, 1371 struct ndb_filter_elements *b_field) 1372 { 1373 int i; 1374 const char *a_str, *b_str; 1375 unsigned char *a_id, *b_id; 1376 uint64_t a_int, b_int; 1377 struct ndb_filter_custom *a_custom, *b_custom; 1378 1379 if (a_field->count != b_field->count) 1380 return 0; 1381 1382 if (a_field->field.type != b_field->field.type) { 1383 ndb_debug("UNUSUAL: field types do not match in ndb_filter_field_eq\n"); 1384 return 0; 1385 } 1386 1387 if (a_field->field.elem_type != b_field->field.elem_type) { 1388 ndb_debug("UNUSUAL: field element types do not match in ndb_filter_field_eq\n"); 1389 return 0; 1390 } 1391 1392 if (a_field->field.elem_type == NDB_ELEMENT_UNKNOWN) { 1393 ndb_debug("UNUSUAL: field element types are unknown\n"); 1394 return 0; 1395 } 1396 1397 for (i = 0; i < a_field->count; i++) { 1398 switch (a_field->field.elem_type) { 1399 case NDB_ELEMENT_CUSTOM: 1400 a_custom = ndb_filter_get_custom_element(a_filt, a_field); 1401 b_custom = ndb_filter_get_custom_element(b_filt, b_field); 1402 if (memcmp(a_custom, b_custom, sizeof(*a_custom))) 1403 return 0; 1404 break; 1405 case NDB_ELEMENT_UNKNOWN: 1406 return 0; 1407 case NDB_ELEMENT_STRING: 1408 a_str = ndb_filter_get_string_element(a_filt, a_field, i); 1409 b_str = ndb_filter_get_string_element(b_filt, b_field, i); 1410 if (strcmp(a_str, b_str)) 1411 return 0; 1412 break; 1413 case NDB_ELEMENT_ID: 1414 a_id = ndb_filter_get_id_element(a_filt, a_field, i); 1415 b_id = ndb_filter_get_id_element(b_filt, b_field, i); 1416 if (memcmp(a_id, b_id, 32)) 1417 return 0; 1418 break; 1419 case NDB_ELEMENT_INT: 1420 a_int = ndb_filter_get_int_element(a_field, i); 1421 b_int = ndb_filter_get_int_element(b_field, i); 1422 if (a_int != b_int) 1423 return 0; 1424 break; 1425 } 1426 } 1427 1428 return 1; 1429 } 1430 1431 void ndb_filter_end_field(struct ndb_filter *filter) 1432 { 1433 int cur_offset; 1434 struct ndb_filter_elements *cur; 1435 1436 cur_offset = filter->current; 1437 1438 if (!(cur = ndb_filter_current_element(filter))) 1439 return; 1440 1441 filter->elements[filter->num_elements++] = cur_offset; 1442 1443 // sort elements for binary search 1444 switch (cur->field.type) { 1445 case NDB_FILTER_IDS: 1446 case NDB_FILTER_AUTHORS: 1447 sort_filter_elements(filter, cur, compare_ids); 1448 break; 1449 case NDB_FILTER_RELAYS: 1450 sort_filter_elements(filter, cur, compare_strs); 1451 break; 1452 case NDB_FILTER_KINDS: 1453 qsort(&cur->elements[0], cur->count, 1454 sizeof(cur->elements[0]), compare_kinds); 1455 break; 1456 case NDB_FILTER_TAGS: 1457 // TODO: generic tag search sorting 1458 break; 1459 case NDB_FILTER_SINCE: 1460 case NDB_FILTER_CUSTOM: 1461 case NDB_FILTER_UNTIL: 1462 case NDB_FILTER_LIMIT: 1463 case NDB_FILTER_SEARCH: 1464 // don't need to sort these 1465 break; 1466 } 1467 1468 filter->current = -1; 1469 1470 } 1471 1472 static void ndb_filter_group_init(struct ndb_filter_group *group) 1473 { 1474 group->num_filters = 0; 1475 } 1476 1477 static int ndb_filter_group_add(struct ndb_filter_group *group, 1478 struct ndb_filter *filter) 1479 { 1480 if (group->num_filters + 1 > MAX_FILTERS) 1481 return 0; 1482 1483 return ndb_filter_clone(&group->filters[group->num_filters++], filter); 1484 } 1485 1486 static int ndb_filter_group_matches(struct ndb_filter_group *group, 1487 struct ndb_note *note) 1488 { 1489 int i; 1490 struct ndb_filter *filter; 1491 1492 if (group->num_filters == 0) 1493 return 1; 1494 1495 for (i = 0; i < group->num_filters; i++) { 1496 filter = &group->filters[i]; 1497 1498 if (ndb_filter_matches(filter, note)) 1499 return 1; 1500 } 1501 1502 return 0; 1503 } 1504 1505 static void ndb_make_search_key(struct ndb_search_key *key, unsigned char *id, 1506 uint64_t timestamp, const char *search) 1507 { 1508 memcpy(key->id, id, 32); 1509 key->timestamp = timestamp; 1510 lowercase_strncpy(key->search, search, sizeof(key->search) - 1); 1511 key->search[sizeof(key->search) - 1] = '\0'; 1512 } 1513 1514 static int ndb_write_profile_search_index(struct ndb_txn *txn, 1515 struct ndb_search_key *index_key, 1516 uint64_t profile_key) 1517 { 1518 int rc; 1519 MDB_val key, val; 1520 1521 key.mv_data = index_key; 1522 key.mv_size = sizeof(*index_key); 1523 val.mv_data = &profile_key; 1524 val.mv_size = sizeof(profile_key); 1525 1526 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_SEARCH], 1527 &key, &val, 0))) 1528 { 1529 ndb_debug("ndb_write_profile_search_index failed: %s\n", 1530 mdb_strerror(rc)); 1531 return 0; 1532 } 1533 1534 return 1; 1535 } 1536 1537 1538 // map usernames and display names to profile keys for user searching 1539 static int ndb_write_profile_search_indices(struct ndb_txn *txn, 1540 struct ndb_note *note, 1541 uint64_t profile_key, 1542 void *profile_root) 1543 { 1544 struct ndb_search_key index; 1545 NdbProfileRecord_table_t profile_record; 1546 NdbProfile_table_t profile; 1547 1548 profile_record = NdbProfileRecord_as_root(profile_root); 1549 profile = NdbProfileRecord_profile_get(profile_record); 1550 1551 const char *name = NdbProfile_name_get(profile); 1552 const char *display_name = NdbProfile_display_name_get(profile); 1553 1554 // words + pubkey + created 1555 if (name) { 1556 ndb_make_search_key(&index, note->pubkey, note->created_at, 1557 name); 1558 if (!ndb_write_profile_search_index(txn, &index, profile_key)) 1559 return 0; 1560 } 1561 1562 if (display_name) { 1563 // don't write the same name/display_name twice 1564 if (name && !strcmp(display_name, name)) { 1565 return 1; 1566 } 1567 ndb_make_search_key(&index, note->pubkey, note->created_at, 1568 display_name); 1569 if (!ndb_write_profile_search_index(txn, &index, profile_key)) 1570 return 0; 1571 } 1572 1573 return 1; 1574 } 1575 1576 static inline void ndb_tsid_init(struct ndb_tsid *key, unsigned char *id, 1577 uint64_t timestamp) 1578 { 1579 memcpy(key->id, id, 32); 1580 key->timestamp = timestamp; 1581 } 1582 1583 static inline void ndb_tsid_low(struct ndb_tsid *key, unsigned char *id) 1584 { 1585 memcpy(key->id, id, 32); 1586 key->timestamp = 0; 1587 } 1588 1589 static inline void ndb_u64_ts_init(struct ndb_u64_ts *key, uint64_t integer, 1590 uint64_t timestamp) 1591 { 1592 key->u64 = integer; 1593 key->timestamp = timestamp; 1594 } 1595 1596 // useful for range-searching for the latest key with a clustered created_at timen 1597 static inline void ndb_tsid_high(struct ndb_tsid *key, const unsigned char *id) 1598 { 1599 memcpy(key->id, id, 32); 1600 key->timestamp = UINT64_MAX; 1601 } 1602 1603 static int _ndb_begin_query(struct ndb *ndb, struct ndb_txn *txn, int flags) 1604 { 1605 txn->lmdb = &ndb->lmdb; 1606 MDB_txn **mdb_txn = (MDB_txn **)&txn->mdb_txn; 1607 if (!txn->lmdb->env) 1608 return 0; 1609 return mdb_txn_begin(txn->lmdb->env, NULL, flags, mdb_txn) == 0; 1610 } 1611 1612 int ndb_begin_query(struct ndb *ndb, struct ndb_txn *txn) 1613 { 1614 return _ndb_begin_query(ndb, txn, MDB_RDONLY); 1615 } 1616 1617 // this should only be used in migrations, etc 1618 static int ndb_begin_rw_query(struct ndb *ndb, struct ndb_txn *txn) 1619 { 1620 return _ndb_begin_query(ndb, txn, 0); 1621 } 1622 1623 static int ndb_db_is_index(enum ndb_dbs index) 1624 { 1625 switch (index) { 1626 case NDB_DB_NOTE: 1627 case NDB_DB_META: 1628 case NDB_DB_PROFILE: 1629 case NDB_DB_NOTE_ID: 1630 case NDB_DB_NDB_META: 1631 case NDB_DB_PROFILE_SEARCH: 1632 case NDB_DB_PROFILE_LAST_FETCH: 1633 case NDB_DB_NOTE_RELAYS: 1634 case NDB_DBS: 1635 return 0; 1636 case NDB_DB_PROFILE_PK: 1637 case NDB_DB_NOTE_KIND: 1638 case NDB_DB_NOTE_TEXT: 1639 case NDB_DB_NOTE_BLOCKS: 1640 case NDB_DB_NOTE_TAGS: 1641 case NDB_DB_NOTE_PUBKEY: 1642 case NDB_DB_NOTE_PUBKEY_KIND: 1643 case NDB_DB_NOTE_RELAY_KIND: 1644 return 1; 1645 } 1646 1647 return 0; 1648 } 1649 1650 static inline void ndb_id_u64_ts_init(struct ndb_id_u64_ts *key, 1651 unsigned char *id, uint64_t iu64, 1652 uint64_t timestamp) 1653 { 1654 memcpy(key->id, id, 32); 1655 key->u64 = iu64; 1656 key->timestamp = timestamp; 1657 } 1658 1659 // formats the relay url buffer for the NDB_DB_NOTE_RELAYS value. It's a 1660 // null terminated string padded to 8 bytes (we must keep the entire database 1661 // aligned to 8 bytes at all times) 1662 static int prepare_relay_buf(char *relay_buf, int bufsize, const char *relay, 1663 int relay_len) 1664 { 1665 struct cursor cur; 1666 1667 // make sure the size of the buffer is aligned 1668 assert((bufsize % 8) == 0); 1669 1670 make_cursor((unsigned char *)relay_buf, (unsigned char *)relay_buf + bufsize, &cur); 1671 1672 // push the relay string 1673 if (!cursor_push(&cur, (unsigned char *)relay, relay_len)) 1674 return 0; 1675 1676 // relay urls are null terminated for convenience 1677 if (!cursor_push_byte(&cur, 0)) 1678 return 0; 1679 1680 // align the buffer 1681 if (!cursor_align(&cur, 8)) 1682 return 0; 1683 1684 return cur.p - cur.start; 1685 } 1686 1687 // Write to the note_id -> relay_url database. This records where notes 1688 // have been seen 1689 static int ndb_write_note_relay(struct ndb_txn *txn, uint64_t note_key, 1690 const char *relay, uint8_t relay_len) 1691 { 1692 char relay_buf[256]; 1693 int rc, len; 1694 MDB_val k, v; 1695 1696 if (relay == NULL || relay_len == 0) { 1697 ndb_debug("relay is NULL in ndb_write_note_relay? '%s' %d\n", relay, relay_len); 1698 return 0; 1699 } 1700 1701 ndb_debug("writing note_relay '%s' for notekey:%" PRIu64 "\n", relay, note_key); 1702 1703 if (!(len = prepare_relay_buf(relay_buf, sizeof(relay_buf), relay, relay_len))) { 1704 fprintf(stderr, "relay url '%s' too large when writing note relay index\n", relay); 1705 return 0; 1706 } 1707 1708 assert((len % 8) == 0); 1709 1710 k.mv_data = ¬e_key; 1711 k.mv_size = sizeof(note_key); 1712 1713 v.mv_data = relay_buf; 1714 v.mv_size = len; 1715 1716 // NODUPDATA is specified so that we don't accidently add duplicate 1717 // key/value pairs 1718 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAYS], 1719 &k, &v, MDB_NODUPDATA))) 1720 { 1721 ndb_debug("ndb_write_note_relay failed: %s\n", mdb_strerror(rc)); 1722 return 0; 1723 } 1724 1725 ndb_debug("wrote %d bytes to note relay: '%s'\n", len, relay_buf); 1726 1727 return 1; 1728 } 1729 1730 struct ndb_relay_kind_key { 1731 uint64_t note_key; 1732 uint64_t kind; 1733 uint64_t created_at; 1734 uint8_t relay_len; 1735 const char *relay; 1736 }; 1737 1738 static int ndb_relay_kind_key_init( 1739 struct ndb_relay_kind_key *key, 1740 uint64_t note_key, 1741 uint64_t kind, 1742 uint64_t created_at, 1743 const char *relay) 1744 { 1745 if (relay == NULL) 1746 return 0; 1747 1748 key->relay = relay; 1749 key->relay_len = strlen(relay); 1750 if (key->relay_len > 248) 1751 return 0; 1752 1753 key->note_key = note_key; 1754 key->kind = kind; 1755 key->created_at = created_at; 1756 return 1; 1757 } 1758 1759 1760 // create a range key for a relay kind query 1761 static int ndb_relay_kind_key_init_high( 1762 struct ndb_relay_kind_key *key, 1763 const char *relay, 1764 uint64_t kind, 1765 uint64_t until) 1766 { 1767 return ndb_relay_kind_key_init(key, UINT64_MAX, kind, UINT64_MAX, relay); 1768 } 1769 1770 static void ndb_parse_relay_kind_key(struct ndb_relay_kind_key *key, unsigned char *buf) 1771 { 1772 // WE ARE ASSUMING WE ARE PARSING FROM AN ALIGNED BUFFER HERE 1773 assert((uint64_t)buf % 8 == 0); 1774 // - note_id: 00 + 8 bytes 1775 // - kind: 08 + 8 bytes 1776 // - created_at: 16 + 8 bytes 1777 // - relay_url_size: 24 + 1 byte 1778 // - relay_url: 25 + n byte null-terminated string 1779 // - pad to 8 byte alignment 1780 key->note_key = *(uint64_t*) (buf + 0); 1781 key->kind = *(uint64_t*) (buf + 8); 1782 key->created_at = *(uint64_t*) (buf + 16); 1783 key->relay_len = *(uint8_t*) (buf + 24); 1784 key->relay = (const char*) (buf + 25); 1785 } 1786 1787 static void ndb_debug_relay_kind_key(struct ndb_relay_kind_key *key) 1788 { 1789 ndb_debug("note_key:%" PRIu64 " kind:%" PRIu64 " created_at:%" PRIu64 " '%s'\n", 1790 key->note_key, key->kind, key->created_at, key->relay); 1791 } 1792 1793 static int ndb_build_relay_kind_key(unsigned char *buf, int bufsize, struct ndb_relay_kind_key *key) 1794 { 1795 struct cursor cur; 1796 make_cursor(buf, buf + bufsize, &cur); 1797 1798 if (!cursor_push(&cur, (unsigned char *)&key->note_key, 8)) return 0; 1799 if (!cursor_push(&cur, (unsigned char *)&key->kind, 8)) return 0; 1800 if (!cursor_push(&cur, (unsigned char *)&key->created_at, 8)) return 0; 1801 if (!cursor_push_byte(&cur, key->relay_len)) return 0; 1802 if (!cursor_push(&cur, (unsigned char *)key->relay, key->relay_len)) return 0; 1803 if (!cursor_push_byte(&cur, 0)) return 0; 1804 if (!cursor_align(&cur, 8)) return 0; 1805 1806 assert(((cur.p-cur.start)%8) == 0); 1807 1808 return cur.p - cur.start; 1809 } 1810 1811 static int ndb_write_note_relay_kind_index( 1812 struct ndb_txn *txn, 1813 struct ndb_relay_kind_key *key) 1814 { 1815 // The relay kind key has a layout like so 1816 // 1817 // - note_key: 00 + 8 bytes 1818 // - kind: 08 + 8 bytes 1819 // - created_at: 16 + 8 bytes 1820 // - relay_url_size: 24 + 1 byte 1821 // - relay_url: 25 + n byte null-terminated string 1822 // - pad to 8 byte alignment 1823 1824 unsigned char buf[256]; 1825 int rc, len; 1826 MDB_val k, v; 1827 1828 // come on bro 1829 if (key->relay_len > 248 || key->relay == NULL || key->relay_len == 0) 1830 return 0; 1831 1832 ndb_debug("writing note_relay_kind_index '%s' for notekey:%" PRIu64 "\n", key->relay, key->note_key); 1833 1834 if (!(len = ndb_build_relay_kind_key(buf, sizeof(buf), key))) 1835 return 0; 1836 1837 k.mv_data = buf; 1838 k.mv_size = len; 1839 1840 v.mv_data = NULL; 1841 v.mv_size = 0; 1842 1843 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAY_KIND], &k, &v, 0))) { 1844 fprintf(stderr, "write note relay kind index failed: %s\n", 1845 mdb_strerror(rc)); 1846 return 0; 1847 } 1848 1849 return 1; 1850 } 1851 1852 // writes the relay note kind index and the note_id -> relay db 1853 static int ndb_write_note_relay_indexes(struct ndb_txn *txn, struct ndb_relay_kind_key *key) 1854 { 1855 ndb_write_note_relay_kind_index(txn, key); 1856 ndb_write_note_relay(txn, key->note_key, key->relay, key->relay_len); 1857 return 1; 1858 } 1859 1860 static int ndb_write_note_pubkey_index(struct ndb_txn *txn, struct ndb_note *note, 1861 uint64_t note_key) 1862 { 1863 int rc; 1864 struct ndb_tsid key; 1865 MDB_val k, v; 1866 1867 ndb_tsid_init(&key, ndb_note_pubkey(note), ndb_note_created_at(note)); 1868 1869 k.mv_data = &key; 1870 k.mv_size = sizeof(key); 1871 1872 v.mv_data = ¬e_key; 1873 v.mv_size = sizeof(note_key); 1874 1875 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY], &k, &v, 0))) { 1876 fprintf(stderr, "write note pubkey index failed: %s\n", 1877 mdb_strerror(rc)); 1878 return 0; 1879 } 1880 1881 return 1; 1882 } 1883 1884 static int ndb_write_note_pubkey_kind_index(struct ndb_txn *txn, 1885 struct ndb_note *note, 1886 uint64_t note_key) 1887 { 1888 int rc; 1889 struct ndb_id_u64_ts key; 1890 MDB_val k, v; 1891 1892 ndb_id_u64_ts_init(&key, ndb_note_pubkey(note), ndb_note_kind(note), 1893 ndb_note_created_at(note)); 1894 1895 k.mv_data = &key; 1896 k.mv_size = sizeof(key); 1897 1898 v.mv_data = ¬e_key; 1899 v.mv_size = sizeof(note_key); 1900 1901 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND], &k, &v, 0))) { 1902 fprintf(stderr, "write note pubkey_kind index failed: %s\n", 1903 mdb_strerror(rc)); 1904 return 0; 1905 } 1906 1907 return 1; 1908 } 1909 1910 1911 static int ndb_rebuild_note_indices(struct ndb_txn *txn, enum ndb_dbs *indices, int num_indices) 1912 { 1913 MDB_val k, v; 1914 MDB_cursor *cur; 1915 int i, drop_dbi, count, rc; 1916 uint64_t note_key; 1917 struct ndb_note *note; 1918 enum ndb_dbs index; 1919 1920 // 0 means empty, not delete the dbi 1921 drop_dbi = 0; 1922 1923 // ensure they are all index dbs 1924 for (i = 0; i < num_indices; i++) { 1925 if (!ndb_db_is_index(indices[i])) { 1926 fprintf(stderr, "ndb_rebuild_note_indices: %s is not an index db\n", ndb_db_name(indices[i])); 1927 return -1; 1928 } 1929 } 1930 1931 // empty the index dbs before we rebuild 1932 for (i = 0; i < num_indices; i++) { 1933 index = indices[i]; 1934 if (mdb_drop(txn->mdb_txn, index, drop_dbi)) { 1935 fprintf(stderr, "ndb_rebuild_note_indices: mdb_drop failed for %s\n", ndb_db_name(index)); 1936 return -1; 1937 } 1938 } 1939 1940 if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE], &cur))) { 1941 fprintf(stderr, "ndb_rebuild_note_indices: mdb_cursor_open failed, error %d\n", rc); 1942 return -1; 1943 } 1944 1945 count = 0; 1946 1947 // loop through all notes and write search indices 1948 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 1949 note = v.mv_data; 1950 note_key = *((uint64_t*)k.mv_data); 1951 1952 for (i = 0; i < num_indices; i++) { 1953 index = indices[i]; 1954 switch (index) { 1955 case NDB_DB_NOTE: 1956 case NDB_DB_META: 1957 case NDB_DB_PROFILE: 1958 case NDB_DB_NOTE_ID: 1959 case NDB_DB_NDB_META: 1960 case NDB_DB_PROFILE_SEARCH: 1961 case NDB_DB_PROFILE_LAST_FETCH: 1962 case NDB_DB_NOTE_RELAYS: 1963 case NDB_DBS: 1964 // this should never happen since we check at 1965 // the start 1966 count = -1; 1967 goto cleanup; 1968 case NDB_DB_PROFILE_PK: 1969 case NDB_DB_NOTE_KIND: 1970 case NDB_DB_NOTE_TEXT: 1971 case NDB_DB_NOTE_BLOCKS: 1972 case NDB_DB_NOTE_TAGS: 1973 fprintf(stderr, "%s index rebuild not supported yet. sorry.\n", ndb_db_name(index)); 1974 count = -1; 1975 goto cleanup; 1976 case NDB_DB_NOTE_PUBKEY: 1977 if (!ndb_write_note_pubkey_index(txn, note, note_key)) { 1978 count = -1; 1979 goto cleanup; 1980 } 1981 break; 1982 case NDB_DB_NOTE_RELAY_KIND: 1983 fprintf(stderr, "it doesn't make sense to rebuild note relay kind index\n"); 1984 return 0; 1985 case NDB_DB_NOTE_PUBKEY_KIND: 1986 if (!ndb_write_note_pubkey_kind_index(txn, note, note_key)) { 1987 count = -1; 1988 goto cleanup; 1989 } 1990 break; 1991 } 1992 } 1993 1994 count++; 1995 } 1996 1997 cleanup: 1998 mdb_cursor_close(cur); 1999 2000 return count; 2001 } 2002 2003 int ndb_cursor_start(MDB_cursor *cur, MDB_val *k, MDB_val *v); 2004 2005 // find the last id tag in a note (e, p, etc) 2006 static unsigned char *ndb_note_last_id_tag(struct ndb_note *note, char type) 2007 { 2008 unsigned char *last = NULL; 2009 struct ndb_iterator iter; 2010 struct ndb_str str; 2011 2012 // get the liked event id (last id) 2013 ndb_tags_iterate_start(note, &iter); 2014 2015 while (ndb_tags_iterate_next(&iter)) { 2016 if (iter.tag->count < 2) 2017 continue; 2018 2019 str = ndb_tag_str(note, iter.tag, 0); 2020 2021 // assign liked to the last e tag 2022 if (str.flag == NDB_PACKED_STR && str.str[0] == type) { 2023 str = ndb_tag_str(note, iter.tag, 1); 2024 if (str.flag == NDB_PACKED_ID) 2025 last = str.id; 2026 } 2027 } 2028 2029 return last; 2030 } 2031 2032 /* get reply information from a note */ 2033 static void ndb_parse_reply(struct ndb_note *note, struct ndb_note_reply *note_reply) 2034 { 2035 unsigned char *root, *reply, *mention, *id; 2036 const char *marker; 2037 struct ndb_iterator iter; 2038 struct ndb_str str; 2039 uint16_t count; 2040 int any_marker, first; 2041 2042 any_marker = 0; 2043 first = 1; 2044 root = NULL; 2045 reply = NULL; 2046 mention = NULL; 2047 2048 // get the liked event id (last id) 2049 ndb_tags_iterate_start(note, &iter); 2050 while (ndb_tags_iterate_next(&iter)) { 2051 if (root && reply && mention) 2052 break; 2053 2054 marker = NULL; 2055 count = ndb_tag_count(iter.tag); 2056 2057 if (count < 2) 2058 continue; 2059 2060 str = ndb_tag_str(note, iter.tag, 0); 2061 if (!(str.flag == NDB_PACKED_STR && str.str[0] == 'e')) 2062 continue; 2063 2064 str = ndb_tag_str(note, iter.tag, 1); 2065 if (str.flag != NDB_PACKED_ID) 2066 continue; 2067 id = str.id; 2068 2069 /* if we have the marker, assign it */ 2070 if (count >= 4) { 2071 str = ndb_tag_str(note, iter.tag, 3); 2072 if (str.flag == NDB_PACKED_STR) 2073 marker = str.str; 2074 } 2075 2076 if (marker) { 2077 any_marker = true; 2078 if (!strcmp(marker, "root")) 2079 root = id; 2080 else if (!strcmp(marker, "reply")) 2081 reply = id; 2082 else if (!strcmp(marker, "mention")) 2083 mention = id; 2084 } else if (!any_marker && first) { 2085 root = id; 2086 first = 0; 2087 } else if (!any_marker && !reply) { 2088 reply = id; 2089 } 2090 } 2091 2092 note_reply->reply = reply; 2093 note_reply->root = root; 2094 note_reply->mention = mention; 2095 } 2096 2097 static int ndb_is_reply_to_root(struct ndb_note_reply *reply) 2098 { 2099 if (reply->root && !reply->reply) 2100 return 1; 2101 else if (reply->root && reply->reply) 2102 return !memcmp(reply->root, reply->reply, 32); 2103 else 2104 return 0; 2105 } 2106 2107 2108 int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies) 2109 { 2110 MDB_val k, v; 2111 MDB_cursor *cur; 2112 MDB_dbi db; 2113 2114 int rc; 2115 uint64_t note_key; 2116 size_t size; 2117 struct ndb_note *note; 2118 unsigned char *keybuf, *reply_id; 2119 struct ndb_note_reply reply; 2120 char buffer[41]; /* 1 + 32 + 8 */ 2121 2122 *direct_replies = 0; 2123 *thread_replies = 0; 2124 2125 db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; 2126 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { 2127 fprintf(stderr, "ndb_count_reactions: mdb_cursor_open failed, error %d\n", rc); 2128 return 0; 2129 } 2130 2131 buffer[0] = 'e'; 2132 memcpy(&buffer[1], note_id, 32); 2133 memset(&buffer[33], 0x00, 8); 2134 2135 k.mv_data = buffer; 2136 k.mv_size = sizeof(buffer); 2137 v.mv_data = NULL; 2138 v.mv_size = 0; 2139 2140 if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE)) 2141 goto cleanup; 2142 2143 do { 2144 keybuf = (unsigned char *)k.mv_data; 2145 note_key = *((uint64_t*)v.mv_data); 2146 if (k.mv_size < sizeof(buffer)) 2147 break; 2148 if (keybuf[0] != 'e') 2149 break; 2150 if (memcmp(&keybuf[1], note_id, 32)) 2151 break; 2152 if (!(note = ndb_get_note_by_key(txn, note_key, &size))) 2153 continue; 2154 if (ndb_note_kind(note) != 1) 2155 continue; 2156 2157 ndb_parse_reply(note, &reply); 2158 2159 if (ndb_is_reply_to_root(&reply)) { 2160 reply_id = reply.root; 2161 } else { 2162 reply_id = reply.reply; 2163 } 2164 2165 if (reply_id && !memcmp(reply_id, note_id, 32)) { 2166 (*direct_replies)++; 2167 } 2168 2169 if (reply.root && !memcmp(reply.root, note_id, 32)) { 2170 (*thread_replies)++; 2171 } 2172 2173 } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); 2174 2175 cleanup: 2176 mdb_cursor_close(cur); 2177 return 1; 2178 } 2179 2180 /* count all of the reactions for a note */ 2181 int ndb_rebuild_reaction_metadata(struct ndb_txn *txn, const unsigned char *note_id, struct ndb_note_meta_builder *builder, uint32_t *count) 2182 { 2183 MDB_val k, v; 2184 MDB_cursor *cur; 2185 MDB_dbi db; 2186 2187 int rc; 2188 uint64_t note_key; 2189 size_t size; 2190 struct ndb_note *note; 2191 unsigned char *keybuf, *last_id; 2192 struct ndb_note_meta_entry *entry; 2193 union ndb_reaction_str reaction_str; 2194 char buffer[41]; /* 1 + 32 + 8 */ 2195 *count = 0; 2196 2197 db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; 2198 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { 2199 fprintf(stderr, "ndb_count_reactions: mdb_cursor_open failed, error %d\n", rc); 2200 return 0; 2201 } 2202 2203 buffer[0] = 'e'; 2204 memcpy(&buffer[1], note_id, 32); 2205 memset(&buffer[33], 0x00, 8); 2206 2207 k.mv_data = buffer; 2208 k.mv_size = sizeof(buffer); 2209 v.mv_data = NULL; 2210 v.mv_size = 0; 2211 2212 if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE)) 2213 goto cleanup; 2214 2215 do { 2216 keybuf = (unsigned char *)k.mv_data; 2217 note_key = *((uint64_t*)v.mv_data); 2218 if (k.mv_size < sizeof(buffer)) 2219 break; 2220 if (keybuf[0] != 'e') 2221 break; 2222 if (memcmp(&keybuf[1], note_id, 32)) 2223 break; 2224 if (!(note = ndb_get_note_by_key(txn, note_key, &size))) 2225 continue; 2226 if (ndb_note_kind(note) != 7) 2227 continue; 2228 if (!(last_id = ndb_note_last_id_tag(note, 'e'))) 2229 continue; 2230 if (memcmp(last_id, note_id, 32)) 2231 continue; 2232 2233 if (builder) { 2234 if (!ndb_reaction_set(&reaction_str, ndb_note_content(note))) 2235 ndb_reaction_set(&reaction_str, "+"); 2236 2237 if ((entry = ndb_note_meta_builder_find_entry(builder, NDB_NOTE_META_REACTION, &reaction_str.binmoji))) { 2238 (*ndb_note_meta_reaction_count(entry))++; 2239 } else if ((entry = ndb_note_meta_add_entry(builder))) { 2240 ndb_note_meta_reaction_set(entry, 1, reaction_str); 2241 } else { 2242 /* couldn't add reaction entry ? */ 2243 ndb_debug("ndb_rebuild_note_indices: couldn't add reaction count entry to metadata builder\n"); 2244 } 2245 } 2246 2247 (*count)++; 2248 } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); 2249 2250 cleanup: 2251 mdb_cursor_close(cur); 2252 return 1; 2253 } 2254 2255 static int ndb_count_reposts(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count) 2256 { 2257 MDB_val k, v; 2258 MDB_cursor *cur; 2259 MDB_dbi db; 2260 int rc; 2261 unsigned char *keybuf; 2262 struct ndb_note *note; 2263 uint64_t note_key, kind; 2264 char buffer[41]; /* 1 + 32 + 8 */ 2265 2266 *count = 0; 2267 db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; 2268 2269 /* we will iterate q tags for this particular id */ 2270 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { 2271 fprintf(stderr, "ndb_count_quotes: mdb_cursor_open failed, error %d\n", rc); 2272 return 0; 2273 } 2274 2275 buffer[0] = 'e'; 2276 memcpy(&buffer[1], note_id, 32); 2277 memset(&buffer[33], 0x00, 8); 2278 2279 k.mv_data = buffer; 2280 k.mv_size = sizeof(buffer); 2281 v.mv_data = NULL; 2282 v.mv_size = 0; 2283 2284 if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE)) 2285 goto cleanup; 2286 2287 do { 2288 keybuf = (unsigned char *)k.mv_data; 2289 note_key = *((uint64_t*)v.mv_data); 2290 2291 if (k.mv_size < sizeof(buffer)) 2292 break; 2293 if (keybuf[0] != 'e') 2294 break; 2295 if (memcmp(&keybuf[1], note_id, 32) != 0) 2296 break; 2297 if (!(note = ndb_get_note_by_key(txn, note_key, NULL))) 2298 continue; 2299 kind = ndb_note_kind(note); 2300 if (!(kind == 6 || kind == 16)) 2301 continue; 2302 (*count)++; 2303 } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); 2304 2305 cleanup: 2306 mdb_cursor_close(cur); 2307 return 1; 2308 } 2309 2310 /* count all of the quote reposts for a note id */ 2311 static int ndb_count_quotes(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count) 2312 { 2313 MDB_val k, v; 2314 MDB_cursor *cur; 2315 MDB_dbi db; 2316 int rc; 2317 unsigned char *keybuf; 2318 char buffer[41]; /* 1 + 32 + 8 */ 2319 2320 *count = 0; 2321 db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; 2322 2323 /* we will iterate q tags for this particular id */ 2324 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { 2325 fprintf(stderr, "ndb_count_quotes: mdb_cursor_open failed, error %d\n", rc); 2326 return 0; 2327 } 2328 2329 buffer[0] = 'q'; 2330 memcpy(&buffer[1], note_id, 32); 2331 memset(&buffer[33], 0x00, 8); 2332 2333 k.mv_data = buffer; 2334 k.mv_size = sizeof(buffer); 2335 v.mv_data = NULL; 2336 v.mv_size = 0; 2337 2338 if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE)) 2339 goto cleanup; 2340 2341 for (;;) { 2342 keybuf = (unsigned char *)k.mv_data; 2343 if (k.mv_size < sizeof(buffer)) 2344 break; 2345 if (keybuf[0] != 'q') 2346 break; 2347 if (memcmp(&keybuf[1], note_id, 32) != 0) 2348 break; 2349 /* TODO(jb55): technically we should check to see if this is a kind 1. 2350 * there could be other kinds with q tags that reference this note 2351 * 2352 * Starting to think we should have tag-kind index 2353 */ 2354 (*count)++; 2355 2356 if (mdb_cursor_get(cur, &k, &v, MDB_NEXT)) 2357 break; 2358 } 2359 2360 cleanup: 2361 mdb_cursor_close(cur); 2362 return 1; 2363 } 2364 2365 /* count quotes and add them to a metadata builder. 2366 * we assume there is no existing quotes entry */ 2367 static int ndb_note_meta_builder_counts(struct ndb_txn *txn, 2368 unsigned char *note_id, 2369 struct ndb_note_meta_builder *builder) 2370 { 2371 uint32_t thread_replies, total_reactions; 2372 uint16_t direct_replies, quotes, reposts; 2373 struct ndb_note_meta_entry *entry; 2374 int rcs[4]; 2375 2376 quotes = 0; 2377 direct_replies = 0; 2378 thread_replies = 0; 2379 total_reactions = 0; 2380 2381 rcs[0] = ndb_rebuild_reaction_metadata(txn, note_id, builder, &total_reactions); 2382 rcs[1] = ndb_count_quotes(txn, note_id, "es); 2383 rcs[2] = ndb_count_replies(txn, note_id, &direct_replies, &thread_replies); 2384 rcs[3] = ndb_count_reposts(txn, note_id, &reposts); 2385 2386 if (!rcs[0] && !rcs[1] && !rcs[2] && !rcs[3]) { 2387 return 0; 2388 } 2389 2390 /* no entry needed */ 2391 if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0 && reposts == 0) { 2392 return 0; 2393 } 2394 2395 if (!(entry = ndb_note_meta_add_entry(builder))) { 2396 return 0; 2397 } 2398 2399 ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies, reposts); 2400 2401 return 1; 2402 } 2403 2404 /* count all of the reactions on a note and add it to metadata in progress */ 2405 static void ndb_note_meta_builder_count_reactions(struct ndb_txn *txn, struct ndb_note_meta_builder *builder) 2406 { 2407 } 2408 2409 2410 // Migrations 2411 // 2412 2413 /* switch from flatbuffer stats to custom v2 */ 2414 static int ndb_migrate_metadata(struct ndb_txn *txn) 2415 { 2416 MDB_val k, k2, v, v2; 2417 MDB_cursor *cur; 2418 MDB_dbi note_db, meta_db; 2419 unsigned char *id; 2420 size_t scratch_size = 1024 * 1024; 2421 unsigned char *buffer = malloc(scratch_size); 2422 int rc, count; 2423 struct ndb_note_meta_builder builder; 2424 struct ndb_note *note; 2425 struct ndb_note_meta *meta; 2426 2427 meta_db = txn->lmdb->dbs[NDB_DB_META]; 2428 note_db = txn->lmdb->dbs[NDB_DB_NOTE]; 2429 2430 /* drop metadata table to avoid issues */ 2431 if (mdb_drop(txn->mdb_txn, meta_db, 0)) { 2432 fprintf(stderr, "ndb_migrate_metadata: mdb_drop failed\n"); 2433 return -1; 2434 } 2435 2436 if ((rc = mdb_cursor_open(txn->mdb_txn, note_db, &cur))) { 2437 fprintf(stderr, "ndb_migrate_metadata: mdb_cursor_open failed, error %d\n", rc); 2438 return -1; 2439 } 2440 2441 count = 0; 2442 2443 /* loop through every metadata entry */ 2444 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 2445 ndb_note_meta_builder_init(&builder, buffer, scratch_size); 2446 2447 note = (struct ndb_note *)v.mv_data; 2448 id = ndb_note_id(note); 2449 k2.mv_data = (unsigned char *)id; 2450 k2.mv_size = 32; 2451 2452 rc = ndb_note_meta_builder_counts(txn, id, &builder); 2453 if (!rc) { 2454 mdb_del(txn->mdb_txn, meta_db, &k2, NULL); 2455 continue; 2456 } 2457 2458 ndb_note_meta_build(&builder, &meta); 2459 assert(ndb_note_meta_entries(meta)->type != 0); 2460 2461 v2.mv_data = meta; 2462 v2.mv_size = ndb_note_meta_total_size(meta); 2463 2464 /* set entry */ 2465 if ((rc = mdb_put(txn->mdb_txn, meta_db, &k2, &v2, 0))) { 2466 ndb_debug("migrate metadata entry failed on write: %s\n", mdb_strerror(rc)); 2467 } 2468 2469 count++; 2470 } 2471 2472 fprintf(stderr, "nostrdb: migrated %d metadata entries\n", count); 2473 2474 free(buffer); 2475 mdb_cursor_close(cur); 2476 return 1; 2477 } 2478 2479 // This was before we had note_profile_pubkey{,_kind} indices. Let's create them. 2480 static int ndb_migrate_profile_indices(struct ndb_txn *txn) 2481 { 2482 int count; 2483 2484 enum ndb_dbs indices[] = {NDB_DB_NOTE_PUBKEY, NDB_DB_NOTE_PUBKEY_KIND}; 2485 if ((count = ndb_rebuild_note_indices(txn, indices, 2)) != -1) { 2486 fprintf(stderr, "migrated %d notes to have pubkey and pubkey_kind indices\n", count); 2487 return 1; 2488 } else { 2489 fprintf(stderr, "error migrating notes to have pubkey and pubkey_kind indices, aborting.\n"); 2490 return 0; 2491 } 2492 } 2493 2494 static int ndb_migrate_user_search_indices(struct ndb_txn *txn) 2495 { 2496 int rc; 2497 MDB_cursor *cur; 2498 MDB_val k, v; 2499 void *profile_root; 2500 NdbProfileRecord_table_t record; 2501 struct ndb_note *note; 2502 uint64_t note_key, profile_key; 2503 size_t len; 2504 int count; 2505 2506 if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE], &cur))) { 2507 fprintf(stderr, "ndb_migrate_user_search_indices: mdb_cursor_open failed, error %d\n", rc); 2508 return 0; 2509 } 2510 2511 count = 0; 2512 2513 // loop through all profiles and write search indices 2514 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 2515 profile_root = v.mv_data; 2516 profile_key = *((uint64_t*)k.mv_data); 2517 record = NdbProfileRecord_as_root(profile_root); 2518 note_key = NdbProfileRecord_note_key(record); 2519 note = ndb_get_note_by_key(txn, note_key, &len); 2520 2521 if (note == NULL) { 2522 continue; 2523 } 2524 2525 if (!ndb_write_profile_search_indices(txn, note, profile_key, 2526 profile_root)) { 2527 fprintf(stderr, "ndb_migrate_user_search_indices: ndb_write_profile_search_indices failed\n"); 2528 continue; 2529 } 2530 2531 count++; 2532 } 2533 2534 fprintf(stderr, "migrated %d profiles to include search indices\n", count); 2535 2536 mdb_cursor_close(cur); 2537 2538 return 1; 2539 } 2540 2541 static int ndb_migrate_lower_user_search_indices(struct ndb_txn *txn) 2542 { 2543 // just drop the search db so we can rebuild it 2544 if (mdb_drop(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_SEARCH], 0)) { 2545 fprintf(stderr, "ndb_migrate_lower_user_search_indices: mdb_drop failed\n"); 2546 return 0; 2547 } 2548 2549 return ndb_migrate_user_search_indices(txn); 2550 } 2551 2552 int ndb_process_profile_note(struct ndb_note *note, struct ndb_profile_record_builder *profile); 2553 2554 2555 int ndb_db_version(struct ndb_txn *txn) 2556 { 2557 uint64_t version, version_key; 2558 MDB_val k, v; 2559 2560 version_key = NDB_META_KEY_VERSION; 2561 k.mv_data = &version_key; 2562 k.mv_size = sizeof(version_key); 2563 2564 if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NDB_META], &k, &v)) { 2565 version = -1; 2566 } else { 2567 if (v.mv_size != 8) { 2568 fprintf(stderr, "run_migrations: invalid version size?"); 2569 return 0; 2570 } 2571 version = *((uint64_t*)v.mv_data); 2572 } 2573 2574 return version; 2575 } 2576 2577 // custom pubkey+kind+timestamp comparison function. This is used by lmdb to 2578 // perform b+ tree searches over the pubkey+kind+timestamp index 2579 static int ndb_id_u64_ts_compare(const MDB_val *a, const MDB_val *b) 2580 { 2581 struct ndb_id_u64_ts *tsa, *tsb; 2582 MDB_val a2 = *a, b2 = *b; 2583 2584 a2.mv_size = sizeof(tsa->id); 2585 b2.mv_size = sizeof(tsb->id); 2586 2587 int cmp = mdb_cmp_memn(&a2, &b2); 2588 if (cmp) return cmp; 2589 2590 tsa = a->mv_data; 2591 tsb = b->mv_data; 2592 2593 if (tsa->u64 < tsb->u64) 2594 return -1; 2595 else if (tsa->u64 > tsb->u64) 2596 return 1; 2597 2598 if (tsa->timestamp < tsb->timestamp) 2599 return -1; 2600 else if (tsa->timestamp > tsb->timestamp) 2601 return 1; 2602 2603 return 0; 2604 } 2605 2606 // custom kind+timestamp comparison function. This is used by lmdb to perform 2607 // b+ tree searches over the kind+timestamp index 2608 static int ndb_u64_ts_compare(const MDB_val *a, const MDB_val *b) 2609 { 2610 struct ndb_u64_ts *tsa, *tsb; 2611 tsa = a->mv_data; 2612 tsb = b->mv_data; 2613 2614 if (tsa->u64 < tsb->u64) 2615 return -1; 2616 else if (tsa->u64 > tsb->u64) 2617 return 1; 2618 2619 if (tsa->timestamp < tsb->timestamp) 2620 return -1; 2621 else if (tsa->timestamp > tsb->timestamp) 2622 return 1; 2623 2624 return 0; 2625 } 2626 2627 static int ndb_tsid_compare(const MDB_val *a, const MDB_val *b) 2628 { 2629 struct ndb_tsid *tsa, *tsb; 2630 MDB_val a2 = *a, b2 = *b; 2631 2632 a2.mv_size = sizeof(tsa->id); 2633 b2.mv_size = sizeof(tsb->id); 2634 2635 int cmp = mdb_cmp_memn(&a2, &b2); 2636 if (cmp) return cmp; 2637 2638 tsa = a->mv_data; 2639 tsb = b->mv_data; 2640 2641 if (tsa->timestamp < tsb->timestamp) 2642 return -1; 2643 else if (tsa->timestamp > tsb->timestamp) 2644 return 1; 2645 return 0; 2646 } 2647 2648 enum ndb_ingester_msgtype { 2649 NDB_INGEST_EVENT, // write json to the ingester queue for processing 2650 NDB_INGEST_QUIT, // kill ingester thread immediately 2651 }; 2652 2653 struct ndb_ingester_event { 2654 const char *relay; 2655 char *json; 2656 unsigned client : 1; // ["EVENT", {...}] messages 2657 unsigned len : 31; 2658 }; 2659 2660 struct ndb_writer_note_relay { 2661 const char *relay; 2662 uint64_t note_key; 2663 uint64_t kind; 2664 uint64_t created_at; 2665 }; 2666 2667 struct ndb_writer_note { 2668 struct ndb_note *note; 2669 size_t note_len; 2670 const char *relay; 2671 }; 2672 2673 static void ndb_writer_note_init(struct ndb_writer_note *writer_note, struct ndb_note *note, size_t note_len, const char *relay) 2674 { 2675 writer_note->note = note; 2676 writer_note->note_len = note_len; 2677 writer_note->relay = relay; 2678 } 2679 2680 struct ndb_writer_profile { 2681 struct ndb_writer_note note; 2682 struct ndb_profile_record_builder record; 2683 }; 2684 2685 struct ndb_ingester_msg { 2686 enum ndb_ingester_msgtype type; 2687 union { 2688 struct ndb_ingester_event event; 2689 }; 2690 }; 2691 2692 struct ndb_writer_ndb_meta { 2693 // these are 64 bit because I'm paranoid of db-wide alignment issues 2694 uint64_t version; 2695 }; 2696 2697 struct ndb_writer_note_meta { 2698 unsigned char note_id[32]; 2699 struct ndb_note_meta *metadata; 2700 }; 2701 2702 // Used in the writer thread when writing ndb_profile_fetch_record's 2703 // kv = pubkey: recor 2704 struct ndb_writer_last_fetch { 2705 unsigned char pubkey[32]; 2706 uint64_t fetched_at; 2707 }; 2708 2709 // write note blocks 2710 struct ndb_writer_blocks { 2711 struct ndb_blocks *blocks; 2712 uint64_t note_key; 2713 }; 2714 2715 // The different types of messages that the writer thread can write to the 2716 // database 2717 struct ndb_writer_msg { 2718 enum ndb_writer_msgtype type; 2719 union { 2720 struct ndb_writer_note_relay note_relay; 2721 struct ndb_writer_note note; 2722 struct ndb_writer_profile profile; 2723 struct ndb_writer_ndb_meta ndb_meta; 2724 struct ndb_writer_last_fetch last_fetch; 2725 struct ndb_writer_blocks blocks; 2726 struct ndb_writer_note_meta note_meta; 2727 }; 2728 }; 2729 2730 static inline int ndb_writer_queue_msg(struct ndb_writer *writer, 2731 struct ndb_writer_msg *msg) 2732 { 2733 return prot_queue_push(&writer->inbox, msg); 2734 } 2735 2736 static uint64_t ndb_write_note_and_profile(struct ndb_txn *txn, struct ndb_writer_profile *profile, unsigned char *scratch, size_t scratch_size, uint32_t ndb_flags); 2737 static int ndb_migrate_utf8_profile_names(struct ndb_txn *txn) 2738 { 2739 int rc; 2740 MDB_cursor *cur; 2741 MDB_val k, v; 2742 void *profile_root; 2743 NdbProfileRecord_table_t record; 2744 struct ndb_note *note, *copied_note; 2745 uint64_t note_key; 2746 size_t len; 2747 int count, failed, ret; 2748 struct ndb_writer_profile profile; 2749 2750 if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE], &cur))) { 2751 fprintf(stderr, "ndb_migrate_utf8_profile_names: mdb_cursor_open failed, error %d\n", rc); 2752 return 0; 2753 } 2754 2755 size_t scratch_size = 8 * 1024 * 1024; 2756 unsigned char *scratch = malloc(scratch_size); 2757 2758 ret = 1; 2759 count = 0; 2760 failed = 0; 2761 2762 // loop through all profiles and write search indices 2763 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 2764 profile_root = v.mv_data; 2765 record = NdbProfileRecord_as_root(profile_root); 2766 note_key = NdbProfileRecord_note_key(record); 2767 note = ndb_get_note_by_key(txn, note_key, &len); 2768 2769 if (note == NULL) { 2770 failed++; 2771 continue; 2772 } 2773 2774 struct ndb_profile_record_builder *b = &profile.record; 2775 2776 // reprocess profile 2777 if (!ndb_process_profile_note(note, b)) { 2778 failed++; 2779 continue; 2780 } 2781 2782 // the writer needs to own this note, and its expected to free it 2783 copied_note = malloc(len); 2784 memcpy(copied_note, note, len); 2785 2786 ndb_writer_note_init(&profile.note, copied_note, len, NULL); 2787 2788 // we don't pass in flags when migrating... a bit sketchy but 2789 // whatever. noone is using this to customize nostrdb atm 2790 if (ndb_write_note_and_profile(txn, &profile, scratch, scratch_size, 0)) { 2791 count++; 2792 } 2793 } 2794 2795 fprintf(stderr, "migrated %d profiles to fix utf8 profile names\n", count); 2796 2797 if (failed != 0) { 2798 fprintf(stderr, "failed to migrate %d profiles to fix utf8 profile names\n", failed); 2799 } 2800 2801 free(scratch); 2802 mdb_cursor_close(cur); 2803 2804 return ret; 2805 } 2806 2807 static struct ndb_migration MIGRATIONS[] = { 2808 { .fn = ndb_migrate_user_search_indices }, 2809 { .fn = ndb_migrate_lower_user_search_indices }, 2810 { .fn = ndb_migrate_utf8_profile_names }, 2811 { .fn = ndb_migrate_profile_indices }, 2812 { .fn = ndb_migrate_metadata }, 2813 }; 2814 2815 2816 int ndb_end_query(struct ndb_txn *txn) 2817 { 2818 // this works on read or write queries. 2819 return mdb_txn_commit(txn->mdb_txn) == 0; 2820 } 2821 2822 int ndb_note_verify(void *ctx, unsigned char *scratch, size_t scratch_size, 2823 struct ndb_note *note) 2824 { 2825 unsigned char id[32]; 2826 secp256k1_xonly_pubkey xonly_pubkey; 2827 int ok; 2828 2829 // first, we ensure the id is valid by calculating the id independently 2830 // from what is given to us 2831 if (!ndb_calculate_id(note, scratch, scratch_size, id)) { 2832 ndb_debug("ndb_note_verify: scratch buffer size too small"); 2833 return 0; 2834 } 2835 2836 if (memcmp(id, note->id, 32)) { 2837 ndb_debug("ndb_note_verify: note id does not match!"); 2838 return 0; 2839 } 2840 2841 // id is ok, let's check signature 2842 2843 ok = secp256k1_xonly_pubkey_parse((secp256k1_context*)ctx, 2844 &xonly_pubkey, 2845 ndb_note_pubkey(note)) != 0; 2846 if (!ok) return 0; 2847 2848 ok = secp256k1_schnorrsig_verify((secp256k1_context*)ctx, 2849 ndb_note_sig(note), id, 32, 2850 &xonly_pubkey) > 0; 2851 if (!ok) return 0; 2852 2853 return 1; 2854 } 2855 2856 static void ndb_writer_last_profile_fetch(struct ndb_txn *txn, 2857 const unsigned char *pubkey, 2858 uint64_t fetched_at) 2859 { 2860 int rc; 2861 MDB_val key, val; 2862 2863 key.mv_data = (unsigned char*)pubkey; 2864 key.mv_size = 32; 2865 val.mv_data = &fetched_at; 2866 val.mv_size = sizeof(fetched_at); 2867 2868 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_LAST_FETCH], 2869 &key, &val, 0))) 2870 { 2871 ndb_debug("write version to ndb_meta failed: %s\n", 2872 mdb_strerror(rc)); 2873 return; 2874 } 2875 2876 //fprintf(stderr, "writing version %" PRIu64 "\n", version); 2877 } 2878 2879 2880 // We just received a profile that we haven't processed yet, but it could 2881 // be an older one! Make sure we only write last fetched profile if it's a new 2882 // one 2883 // 2884 // To do this, we first check the latest profile in the database. If the 2885 // created_date for this profile note is newer, then we write a 2886 // last_profile_fetch record, otherwise we do not. 2887 // 2888 // WARNING: This function is only valid when called from the writer thread 2889 static int ndb_maybe_write_last_profile_fetch(struct ndb_txn *txn, 2890 struct ndb_note *note) 2891 { 2892 size_t len; 2893 uint64_t profile_key, note_key; 2894 void *root; 2895 struct ndb_note *last_profile; 2896 NdbProfileRecord_table_t record; 2897 2898 if ((root = ndb_get_profile_by_pubkey(txn, note->pubkey, &len, &profile_key))) { 2899 record = NdbProfileRecord_as_root(root); 2900 note_key = NdbProfileRecord_note_key(record); 2901 last_profile = ndb_get_note_by_key(txn, note_key, &len); 2902 if (last_profile == NULL) { 2903 return 0; 2904 } 2905 2906 // found profile, let's see if it's newer than ours 2907 if (note->created_at > last_profile->created_at) { 2908 // this is a new profile note, record last fetched time 2909 ndb_writer_last_profile_fetch(txn, note->pubkey, time(NULL)); 2910 } 2911 } else { 2912 // couldn't fetch profile. record last fetched time 2913 ndb_writer_last_profile_fetch(txn, note->pubkey, time(NULL)); 2914 } 2915 2916 return 1; 2917 } 2918 2919 int ndb_write_last_profile_fetch(struct ndb *ndb, const unsigned char *pubkey, 2920 uint64_t fetched_at) 2921 { 2922 struct ndb_writer_msg msg; 2923 msg.type = NDB_WRITER_PROFILE_LAST_FETCH; 2924 memcpy(&msg.last_fetch.pubkey[0], pubkey, 32); 2925 msg.last_fetch.fetched_at = fetched_at; 2926 2927 return ndb_writer_queue_msg(&ndb->writer, &msg); 2928 } 2929 2930 2931 // When doing cursor scans from greatest to lowest, this function positions the 2932 // cursor at the first element before descending. MDB_SET_RANGE puts us right 2933 // after the first element, so we have to go back one. 2934 int ndb_cursor_start(MDB_cursor *cur, MDB_val *k, MDB_val *v) 2935 { 2936 int rc; 2937 // Position cursor at the next key greater than or equal to the 2938 // specified key 2939 2940 if ((rc = mdb_cursor_get(cur, k, v, MDB_SET_RANGE))) { 2941 ndb_debug("MDB_SET_RANGE failed: '%s'\n", mdb_strerror(rc)); 2942 // Failed :(. It could be the last element? 2943 if ((rc = mdb_cursor_get(cur, k, v, MDB_LAST))) { 2944 ndb_debug("MDB_LAST failed: '%s'\n", mdb_strerror(rc)); 2945 return 0; 2946 } 2947 } else { 2948 // if set range worked and our key exists, it should be 2949 // the one right before this one 2950 if ((rc = mdb_cursor_get(cur, k, v, MDB_PREV))) { 2951 ndb_debug("moving back failed: '%s'\n", mdb_strerror(rc)); 2952 return 0; 2953 } 2954 } 2955 2956 return 1; 2957 } 2958 2959 2960 // get some value based on a clustered id key 2961 int ndb_get_tsid(struct ndb_txn *txn, enum ndb_dbs db, const unsigned char *id, 2962 MDB_val *val) 2963 { 2964 MDB_val k, v; 2965 MDB_cursor *cur; 2966 int success = 0, rc; 2967 struct ndb_tsid tsid; 2968 2969 // position at the most recent 2970 ndb_tsid_high(&tsid, id); 2971 2972 k.mv_data = &tsid; 2973 k.mv_size = sizeof(tsid); 2974 2975 if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[db], &cur))) { 2976 ndb_debug("ndb_get_tsid: failed to open cursor: '%s'\n", mdb_strerror(rc)); 2977 return 0; 2978 } 2979 2980 if (!ndb_cursor_start(cur, &k, &v)) 2981 goto cleanup; 2982 2983 if (memcmp(k.mv_data, id, 32) == 0) { 2984 *val = v; 2985 success = 1; 2986 } 2987 2988 cleanup: 2989 mdb_cursor_close(cur); 2990 return success; 2991 } 2992 2993 static void *ndb_lookup_by_key(struct ndb_txn *txn, uint64_t key, 2994 enum ndb_dbs store, size_t *len) 2995 { 2996 MDB_val k, v; 2997 2998 k.mv_data = &key; 2999 k.mv_size = sizeof(key); 3000 3001 if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[store], &k, &v)) { 3002 ndb_debug("ndb_lookup_by_key: mdb_get note failed\n"); 3003 return NULL; 3004 } 3005 3006 if (len) 3007 *len = v.mv_size; 3008 3009 return v.mv_data; 3010 } 3011 3012 static void *ndb_lookup_tsid(struct ndb_txn *txn, enum ndb_dbs ind, 3013 enum ndb_dbs store, const unsigned char *pk, 3014 size_t *len, uint64_t *primkey) 3015 { 3016 MDB_val k, v; 3017 void *res = NULL; 3018 if (len) 3019 *len = 0; 3020 3021 if (!ndb_get_tsid(txn, ind, pk, &k)) { 3022 //ndb_debug("ndb_get_profile_by_pubkey: ndb_get_tsid failed\n"); 3023 return 0; 3024 } 3025 3026 if (primkey) 3027 *primkey = *(uint64_t*)k.mv_data; 3028 3029 if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[store], &k, &v)) { 3030 ndb_debug("ndb_get_profile_by_pubkey: mdb_get note failed\n"); 3031 return 0; 3032 } 3033 3034 res = v.mv_data; 3035 assert(((uint64_t)res % 4) == 0); 3036 if (len) 3037 *len = v.mv_size; 3038 return res; 3039 } 3040 3041 void *ndb_get_profile_by_pubkey(struct ndb_txn *txn, const unsigned char *pk, size_t *len, uint64_t *key) 3042 { 3043 return ndb_lookup_tsid(txn, NDB_DB_PROFILE_PK, NDB_DB_PROFILE, pk, len, key); 3044 } 3045 3046 struct ndb_note *ndb_get_note_by_id(struct ndb_txn *txn, const unsigned char *id, size_t *len, uint64_t *key) 3047 { 3048 return ndb_lookup_tsid(txn, NDB_DB_NOTE_ID, NDB_DB_NOTE, id, len, key); 3049 } 3050 3051 static inline uint64_t ndb_get_indexkey_by_id(struct ndb_txn *txn, 3052 enum ndb_dbs db, 3053 const unsigned char *id) 3054 { 3055 MDB_val k; 3056 3057 if (!ndb_get_tsid(txn, db, id, &k)) 3058 return 0; 3059 3060 return *(uint32_t*)k.mv_data; 3061 } 3062 3063 uint64_t ndb_get_notekey_by_id(struct ndb_txn *txn, const unsigned char *id) 3064 { 3065 return ndb_get_indexkey_by_id(txn, NDB_DB_NOTE_ID, id); 3066 } 3067 3068 uint64_t ndb_get_profilekey_by_pubkey(struct ndb_txn *txn, const unsigned char *id) 3069 { 3070 return ndb_get_indexkey_by_id(txn, NDB_DB_PROFILE_PK, id); 3071 } 3072 3073 struct ndb_note *ndb_get_note_by_key(struct ndb_txn *txn, uint64_t key, size_t *len) 3074 { 3075 return ndb_lookup_by_key(txn, key, NDB_DB_NOTE, len); 3076 } 3077 3078 void *ndb_get_profile_by_key(struct ndb_txn *txn, uint64_t key, size_t *len) 3079 { 3080 return ndb_lookup_by_key(txn, key, NDB_DB_PROFILE, len); 3081 } 3082 3083 uint64_t 3084 ndb_read_last_profile_fetch(struct ndb_txn *txn, const unsigned char *pubkey) 3085 { 3086 MDB_val k, v; 3087 3088 k.mv_data = (unsigned char*)pubkey; 3089 k.mv_size = 32; 3090 3091 if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_PROFILE_LAST_FETCH], &k, &v)) { 3092 //ndb_debug("ndb_read_last_profile_fetch: mdb_get note failed\n"); 3093 return 0; 3094 } 3095 3096 return *((uint64_t*)v.mv_data); 3097 } 3098 3099 3100 static int ndb_has_note(struct ndb_txn *txn, const unsigned char *id) 3101 { 3102 MDB_val val; 3103 3104 if (!ndb_get_tsid(txn, NDB_DB_NOTE_ID, id, &val)) 3105 return 0; 3106 3107 return 1; 3108 } 3109 3110 static void ndb_txn_from_mdb(struct ndb_txn *txn, struct ndb_lmdb *lmdb, 3111 MDB_txn *mdb_txn) 3112 { 3113 txn->lmdb = lmdb; 3114 txn->mdb_txn = mdb_txn; 3115 } 3116 3117 static enum ndb_idres ndb_ingester_json_controller(void *data, const char *hexid) 3118 { 3119 unsigned char id[32]; 3120 struct ndb_ingest_controller *c = data; 3121 struct ndb_txn txn; 3122 3123 hex_decode(hexid, 64, id, sizeof(id)); 3124 3125 // let's see if we already have it 3126 ndb_txn_from_mdb(&txn, c->lmdb, c->read_txn); 3127 c->note = ndb_get_note_by_id(&txn, id, NULL, &c->note_key); 3128 3129 if (c->note == NULL) 3130 return NDB_IDRES_CONT; 3131 3132 return NDB_IDRES_STOP; 3133 } 3134 3135 static int ndbprofile_parse_json(flatcc_builder_t *B, 3136 const char *buf, size_t bufsiz, int flags, NdbProfile_ref_t *profile) 3137 { 3138 flatcc_json_parser_t parser, *ctx = &parser; 3139 flatcc_json_parser_init(ctx, B, buf, buf + bufsiz, flags); 3140 3141 if (flatcc_builder_start_buffer(B, 0, 0, 0)) 3142 return 0; 3143 3144 NdbProfile_parse_json_table(ctx, buf, buf + bufsiz, profile); 3145 if (ctx->error) 3146 return 0; 3147 3148 if (!flatcc_builder_end_buffer(B, *profile)) 3149 return 0; 3150 3151 ctx->end_loc = buf; 3152 3153 3154 return 1; 3155 } 3156 3157 void ndb_profile_record_builder_init(struct ndb_profile_record_builder *b) 3158 { 3159 b->builder = malloc(sizeof(*b->builder)); 3160 b->flatbuf = NULL; 3161 } 3162 3163 void ndb_profile_record_builder_free(struct ndb_profile_record_builder *b) 3164 { 3165 if (b->flatbuf) 3166 flatcc_builder_aligned_free(b->flatbuf); 3167 if (b->builder) { 3168 flatcc_builder_clear(b->builder); 3169 free(b->builder); 3170 } 3171 3172 b->builder = NULL; 3173 b->flatbuf = NULL; 3174 3175 } 3176 3177 int ndb_process_profile_note(struct ndb_note *note, 3178 struct ndb_profile_record_builder *profile) 3179 { 3180 int res; 3181 3182 NdbProfile_ref_t profile_table; 3183 flatcc_builder_t *builder; 3184 3185 ndb_profile_record_builder_init(profile); 3186 builder = profile->builder; 3187 flatcc_builder_init(builder); 3188 3189 NdbProfileRecord_start_as_root(builder); 3190 3191 //printf("parsing profile '%.*s'\n", note->content_length, ndb_note_content(note)); 3192 if (!(res = ndbprofile_parse_json(builder, ndb_note_content(note), 3193 note->content_length, 3194 flatcc_json_parser_f_skip_unknown, 3195 &profile_table))) 3196 { 3197 ndb_debug("profile_parse_json failed %d '%.*s'\n", res, 3198 note->content_length, ndb_note_content(note)); 3199 ndb_profile_record_builder_free(profile); 3200 return 0; 3201 } 3202 3203 uint64_t received_at = time(NULL); 3204 const char *lnurl = "fixme"; 3205 3206 NdbProfileRecord_profile_add(builder, profile_table); 3207 NdbProfileRecord_received_at_add(builder, received_at); 3208 3209 flatcc_builder_ref_t lnurl_off; 3210 lnurl_off = flatcc_builder_create_string_str(builder, lnurl); 3211 3212 NdbProfileRecord_lnurl_add(builder, lnurl_off); 3213 3214 //*profile = flatcc_builder_finalize_aligned_buffer(builder, profile_len); 3215 return 1; 3216 } 3217 3218 static int ndb_ingester_queue_event(struct ndb_ingester *ingester, 3219 char *json, unsigned len, 3220 unsigned client, const char *relay) 3221 { 3222 struct ndb_ingester_msg msg; 3223 msg.type = NDB_INGEST_EVENT; 3224 3225 msg.event.json = json; 3226 msg.event.len = len; 3227 msg.event.client = client; 3228 msg.event.relay = relay; 3229 3230 return threadpool_dispatch(&ingester->tp, &msg); 3231 } 3232 3233 void ndb_ingest_meta_init(struct ndb_ingest_meta *meta, unsigned client, const char *relay) 3234 { 3235 meta->client = client; 3236 meta->relay = relay; 3237 } 3238 3239 static int ndb_ingest_event(struct ndb_ingester *ingester, const char *json, 3240 int len, struct ndb_ingest_meta *meta) 3241 { 3242 const char *relay = meta->relay; 3243 3244 // Without this, we get bus errors in the json parser inside when 3245 // trying to ingest empty kind 6 reposts... we should probably do fuzz 3246 // testing on inputs to the json parser 3247 if (len == 0) 3248 return 0; 3249 3250 // Since we need to return as soon as possible, and we're not 3251 // making any assumptions about the lifetime of the string, we 3252 // definitely need to copy the json here. In the future once we 3253 // have our thread that manages a websocket connection, we can 3254 // avoid the copy and just use the buffer we get from that 3255 // thread. 3256 char *json_copy = strdupn(json, len); 3257 if (json_copy == NULL) 3258 return 0; 3259 3260 if (relay != NULL) { 3261 relay = strdup(meta->relay); 3262 if (relay == NULL) 3263 return 0; 3264 } 3265 3266 return ndb_ingester_queue_event(ingester, json_copy, len, meta->client, relay); 3267 } 3268 3269 3270 static int ndb_ingester_process_note(secp256k1_context *ctx, 3271 struct ndb_note *note, 3272 size_t note_size, 3273 struct ndb_writer_msg *out, 3274 struct ndb_ingester *ingester, 3275 unsigned char *scratch, 3276 const char *relay) 3277 { 3278 enum ndb_ingest_filter_action action; 3279 struct ndb_ingest_meta meta; 3280 3281 action = NDB_INGEST_ACCEPT; 3282 3283 if (ingester->filter) 3284 action = ingester->filter(ingester->filter_context, note); 3285 3286 if (action == NDB_INGEST_REJECT) 3287 return 0; 3288 3289 // some special situations we might want to skip sig validation, 3290 // like during large imports 3291 if (action == NDB_INGEST_SKIP_VALIDATION || (ingester->flags & NDB_FLAG_SKIP_NOTE_VERIFY)) { 3292 // if we're skipping validation we don't need to verify 3293 } else { 3294 // verify! If it's an invalid note we don't need to 3295 // bother writing it to the database 3296 if (!ndb_note_verify(ctx, scratch, ingester->scratch_size, note)) { 3297 ndb_debug("note verification failed\n"); 3298 return 0; 3299 } 3300 } 3301 3302 // we didn't find anything. let's send it 3303 // to the writer thread 3304 note = realloc(note, note_size); 3305 assert(((uint64_t)note % 4) == 0); 3306 3307 if (note->kind == 0) { 3308 struct ndb_profile_record_builder *b = 3309 &out->profile.record; 3310 3311 ndb_process_profile_note(note, b); 3312 3313 out->type = NDB_WRITER_PROFILE; 3314 ndb_writer_note_init(&out->profile.note, note, note_size, relay); 3315 return 1; 3316 } else if (note->kind == 6) { 3317 // process the repost if we have a repost event 3318 ndb_debug("processing kind 6 repost\n"); 3319 // dup the relay string 3320 ndb_ingest_meta_init(&meta, 0, relay); 3321 ndb_ingest_event(ingester, ndb_note_content(note), 3322 ndb_note_content_length(note), 3323 &meta); 3324 } 3325 3326 out->type = NDB_WRITER_NOTE; 3327 ndb_writer_note_init(&out->note, note, note_size, relay); 3328 3329 return 1; 3330 } 3331 3332 int ndb_note_seen_on_relay(struct ndb_txn *txn, uint64_t note_key, const char *relay) 3333 { 3334 MDB_val k, v; 3335 MDB_cursor *cur; 3336 int rc, len; 3337 char relay_buf[256]; 3338 3339 if (relay == NULL) 3340 return 0; 3341 3342 len = strlen(relay); 3343 3344 if (!(len = prepare_relay_buf(relay_buf, sizeof(relay_buf), relay, len))) 3345 return 0; 3346 3347 assert((len % 8) == 0); 3348 3349 k.mv_data = ¬e_key; 3350 k.mv_size = sizeof(note_key); 3351 3352 v.mv_data = relay_buf; 3353 v.mv_size = len; 3354 3355 if ((rc = mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAYS], &cur)) != MDB_SUCCESS) 3356 return 0; 3357 3358 rc = mdb_cursor_get(cur, &k, &v, MDB_GET_BOTH); 3359 ndb_debug("seen_on_relay result: %s\n", mdb_strerror(rc)); 3360 mdb_cursor_close(cur); 3361 3362 return rc == MDB_SUCCESS; 3363 } 3364 3365 // process the relay for the note. this is called when we already have the 3366 // note in the database but still need to check if the relay needs to be 3367 // written to the relay indexes for corresponding note 3368 static int ndb_process_note_relay(struct ndb_txn *txn, struct ndb_writer_msg *out, 3369 uint64_t note_key, struct ndb_note *note, 3370 const char *relay) 3371 { 3372 // query to see if we already have the relay on this note 3373 if (ndb_note_seen_on_relay(txn, note_key, relay)) { 3374 return 0; 3375 } 3376 3377 // if not, tell the writer thread to emit a NOTE_RELAY event 3378 out->type = NDB_WRITER_NOTE_RELAY; 3379 3380 ndb_debug("pushing NDB_WRITER_NOTE_RELAY with note_key %" PRIu64 "\n", note_key); 3381 out->note_relay.relay = relay; 3382 out->note_relay.note_key = note_key; 3383 out->note_relay.kind = ndb_note_kind(note); 3384 out->note_relay.created_at = ndb_note_created_at(note); 3385 3386 return 1; 3387 } 3388 3389 static int ndb_ingester_process_event(secp256k1_context *ctx, 3390 struct ndb_ingester *ingester, 3391 struct ndb_ingester_event *ev, 3392 struct ndb_writer_msg *out, 3393 unsigned char *scratch, 3394 MDB_txn *read_txn) 3395 { 3396 struct ndb_tce tce; 3397 struct ndb_fce fce; 3398 struct ndb_note *note; 3399 struct ndb_ingest_controller controller; 3400 struct ndb_id_cb cb; 3401 void *buf; 3402 int ok; 3403 size_t bufsize, note_size; 3404 3405 ok = 0; 3406 3407 // we will use this to check if we already have it in the DB during 3408 // ID parsing 3409 controller.read_txn = read_txn; 3410 controller.lmdb = ingester->lmdb; 3411 cb.fn = ndb_ingester_json_controller; 3412 cb.data = &controller; 3413 3414 // since we're going to be passing this allocated note to a different 3415 // thread, we can't use thread-local buffers. just allocate a block 3416 bufsize = max(ev->len * 8.0, 4096); 3417 buf = malloc(bufsize); 3418 if (!buf) { 3419 ndb_debug("couldn't malloc buf\n"); 3420 return 0; 3421 } 3422 3423 note_size = 3424 ev->client ? 3425 ndb_client_event_from_json(ev->json, ev->len, &fce, buf, bufsize, &cb) : 3426 ndb_ws_event_from_json(ev->json, ev->len, &tce, buf, bufsize, &cb); 3427 3428 // This is a result from our special json parser. It parsed the id 3429 // and found that we already have it in the database 3430 if ((int)note_size == -42) { 3431 assert(controller.note != NULL); 3432 assert(controller.note_key != 0); 3433 struct ndb_txn txn; 3434 ndb_txn_from_mdb(&txn, ingester->lmdb, read_txn); 3435 3436 // we still need to process the relays on the note even 3437 // if we already have it 3438 if (ev->relay && ndb_process_note_relay(&txn, out, 3439 controller.note_key, 3440 controller.note, 3441 ev->relay)) 3442 { 3443 // free note buf here since we don't pass the note to the writer thread 3444 free(buf); 3445 goto success; 3446 } else { 3447 // we already have the note and there are no new 3448 // relays to process. nothing to write. 3449 goto cleanup; 3450 } 3451 } else if (note_size == 0) { 3452 ndb_debug("failed to parse '%.*s'\n", ev->len, ev->json); 3453 goto cleanup; 3454 } 3455 3456 //ndb_debug("parsed evtype:%d '%.*s'\n", tce.evtype, ev->len, ev->json); 3457 3458 if (ev->client) { 3459 switch (fce.evtype) { 3460 case NDB_FCE_EVENT: 3461 note = fce.event.note; 3462 if (note != buf) { 3463 ndb_debug("note buffer not equal to malloc'd buffer\n"); 3464 goto cleanup; 3465 } 3466 3467 if (!ndb_ingester_process_note(ctx, note, note_size, 3468 out, ingester, scratch, 3469 ev->relay)) { 3470 ndb_debug("failed to process note\n"); 3471 goto cleanup; 3472 } else { 3473 goto success; 3474 } 3475 } 3476 } else { 3477 switch (tce.evtype) { 3478 case NDB_TCE_AUTH: goto cleanup; 3479 case NDB_TCE_NOTICE: goto cleanup; 3480 case NDB_TCE_EOSE: goto cleanup; 3481 case NDB_TCE_OK: goto cleanup; 3482 case NDB_TCE_EVENT: 3483 note = tce.event.note; 3484 if (note != buf) { 3485 ndb_debug("note buffer not equal to malloc'd buffer\n"); 3486 goto cleanup; 3487 } 3488 3489 if (!ndb_ingester_process_note(ctx, note, note_size, 3490 out, ingester, scratch, 3491 ev->relay)) { 3492 ndb_debug("failed to process note\n"); 3493 goto cleanup; 3494 } else { 3495 goto success; 3496 } 3497 } 3498 } 3499 3500 3501 success: 3502 free(ev->json); 3503 // we don't free relay or buf since those are passed to the writer thread 3504 return 1; 3505 3506 cleanup: 3507 free(ev->json); 3508 if (ev->relay) 3509 free((void*)ev->relay); 3510 free(buf); 3511 3512 return ok; 3513 } 3514 3515 static uint64_t ndb_get_last_key(MDB_txn *txn, MDB_dbi db) 3516 { 3517 MDB_cursor *mc; 3518 MDB_val key, val; 3519 3520 if (mdb_cursor_open(txn, db, &mc)) 3521 return 0; 3522 3523 if (mdb_cursor_get(mc, &key, &val, MDB_LAST)) { 3524 mdb_cursor_close(mc); 3525 return 0; 3526 } 3527 3528 mdb_cursor_close(mc); 3529 3530 assert(key.mv_size == 8); 3531 return *((uint64_t*)key.mv_data); 3532 } 3533 3534 // 3535 // make a search key meant for user queries without any other note info 3536 static void ndb_make_search_key_low(struct ndb_search_key *key, const char *search) 3537 { 3538 memset(key->id, 0, sizeof(key->id)); 3539 key->timestamp = 0; 3540 lowercase_strncpy(key->search, search, sizeof(key->search) - 1); 3541 key->search[sizeof(key->search) - 1] = '\0'; 3542 } 3543 3544 int ndb_search_profile(struct ndb_txn *txn, struct ndb_search *search, const char *query) 3545 { 3546 int rc; 3547 struct ndb_search_key s; 3548 MDB_val k, v; 3549 search->cursor = NULL; 3550 3551 MDB_cursor **cursor = (MDB_cursor **)&search->cursor; 3552 3553 ndb_make_search_key_low(&s, query); 3554 3555 k.mv_data = &s; 3556 k.mv_size = sizeof(s); 3557 3558 if ((rc = mdb_cursor_open(txn->mdb_txn, 3559 txn->lmdb->dbs[NDB_DB_PROFILE_SEARCH], 3560 cursor))) { 3561 printf("search_profile: cursor opened failed: %s\n", 3562 mdb_strerror(rc)); 3563 return 0; 3564 } 3565 3566 // Position cursor at the next key greater than or equal to the specified key 3567 if (mdb_cursor_get(search->cursor, &k, &v, MDB_SET_RANGE)) { 3568 printf("search_profile: cursor get failed\n"); 3569 goto cleanup; 3570 } else { 3571 search->key = k.mv_data; 3572 assert(v.mv_size == 8); 3573 search->profile_key = *((uint64_t*)v.mv_data); 3574 return 1; 3575 } 3576 3577 cleanup: 3578 mdb_cursor_close(search->cursor); 3579 search->cursor = NULL; 3580 return 0; 3581 } 3582 3583 void ndb_search_profile_end(struct ndb_search *search) 3584 { 3585 if (search->cursor) 3586 mdb_cursor_close(search->cursor); 3587 } 3588 3589 int ndb_search_profile_next(struct ndb_search *search) 3590 { 3591 int rc; 3592 MDB_val k, v; 3593 unsigned char *init_id; 3594 3595 init_id = search->key->id; 3596 k.mv_data = search->key; 3597 k.mv_size = sizeof(*search->key); 3598 3599 retry: 3600 if ((rc = mdb_cursor_get(search->cursor, &k, &v, MDB_NEXT))) { 3601 ndb_debug("ndb_search_profile_next: %s\n", 3602 mdb_strerror(rc)); 3603 return 0; 3604 } else { 3605 search->key = k.mv_data; 3606 assert(v.mv_size == 8); 3607 search->profile_key = *((uint64_t*)v.mv_data); 3608 3609 // skip duplicate pubkeys 3610 if (!memcmp(init_id, search->key->id, 32)) 3611 goto retry; 3612 } 3613 3614 return 1; 3615 } 3616 3617 // 3618 // The relay kind index has a layout like so (so we don't need dupsort) 3619 // 3620 // - note_id: 00 + 8 bytes 3621 // - kind: 08 + 8 bytes 3622 // - created_at: 16 + 8 bytes 3623 // - relay_url_size: 24 + 1 byte 3624 // - relay_url: 25 + n byte null-terminated string 3625 // - pad to 8 byte alignment 3626 // 3627 // The key sort order is: 3628 // 3629 // relay_url, kind, created_at 3630 // 3631 static int ndb_relay_kind_cmp(const MDB_val *a, const MDB_val *b) 3632 { 3633 int cmp; 3634 MDB_val va, vb; 3635 uint64_t iva, ivb; 3636 unsigned char *ad = (unsigned char *)a->mv_data; 3637 unsigned char *bd = (unsigned char *)b->mv_data; 3638 assert(((uint64_t)a->mv_data % 8) == 0); 3639 3640 va.mv_size = *(ad + 24); 3641 va.mv_data = ad + 25; 3642 3643 vb.mv_size = *(bd + 24); 3644 vb.mv_data = bd + 25; 3645 3646 cmp = mdb_cmp_memn(&va, &vb); 3647 if (cmp) return cmp; 3648 3649 // kind 3650 iva = *(uint64_t*)(ad + 8); 3651 ivb = *(uint64_t*)(bd + 8); 3652 3653 if (iva < ivb) 3654 return -1; 3655 else if (iva > ivb) 3656 return 1; 3657 3658 // created_at 3659 iva = *(uint64_t*)(ad + 16); 3660 ivb = *(uint64_t*)(bd + 16); 3661 3662 if (iva < ivb) 3663 return -1; 3664 else if (iva > ivb) 3665 return 1; 3666 3667 // note_id (so we don't need dupsort logic) 3668 iva = *(uint64_t*)ad; 3669 ivb = *(uint64_t*)bd; 3670 3671 if (iva < ivb) 3672 return -1; 3673 else if (iva > ivb) 3674 return 1; 3675 3676 return 0; 3677 } 3678 3679 static int ndb_search_key_cmp(const MDB_val *a, const MDB_val *b) 3680 { 3681 int cmp; 3682 struct ndb_search_key *ska, *skb; 3683 3684 ska = a->mv_data; 3685 skb = b->mv_data; 3686 3687 MDB_val a2 = *a; 3688 MDB_val b2 = *b; 3689 3690 a2.mv_data = ska->search; 3691 a2.mv_size = sizeof(ska->search) + sizeof(ska->id); 3692 3693 cmp = mdb_cmp_memn(&a2, &b2); 3694 if (cmp) return cmp; 3695 3696 if (ska->timestamp < skb->timestamp) 3697 return -1; 3698 else if (ska->timestamp > skb->timestamp) 3699 return 1; 3700 3701 return 0; 3702 } 3703 3704 static int ndb_write_profile_pk_index(struct ndb_txn *txn, struct ndb_note *note, uint64_t profile_key) 3705 3706 { 3707 MDB_val key, val; 3708 int rc; 3709 struct ndb_tsid tsid; 3710 MDB_dbi pk_db; 3711 3712 pk_db = txn->lmdb->dbs[NDB_DB_PROFILE_PK]; 3713 3714 // write profile_pk + created_at index 3715 ndb_tsid_init(&tsid, note->pubkey, note->created_at); 3716 3717 key.mv_data = &tsid; 3718 key.mv_size = sizeof(tsid); 3719 val.mv_data = &profile_key; 3720 val.mv_size = sizeof(profile_key); 3721 3722 if ((rc = mdb_put(txn->mdb_txn, pk_db, &key, &val, 0))) { 3723 ndb_debug("write profile_pk(%" PRIu64 ") to db failed: %s\n", 3724 profile_key, mdb_strerror(rc)); 3725 return 0; 3726 } 3727 3728 return 1; 3729 } 3730 3731 static int ndb_write_profile(struct ndb_txn *txn, 3732 struct ndb_writer_profile *profile, 3733 uint64_t note_key) 3734 { 3735 uint64_t profile_key; 3736 struct ndb_note *note; 3737 void *flatbuf; 3738 size_t flatbuf_len; 3739 int rc; 3740 3741 MDB_val key, val; 3742 MDB_dbi profile_db; 3743 3744 note = profile->note.note; 3745 3746 // add note_key to profile record 3747 NdbProfileRecord_note_key_add(profile->record.builder, note_key); 3748 NdbProfileRecord_end_as_root(profile->record.builder); 3749 3750 flatbuf = profile->record.flatbuf = 3751 flatcc_builder_finalize_aligned_buffer(profile->record.builder, &flatbuf_len); 3752 3753 assert(((uint64_t)flatbuf % 8) == 0); 3754 3755 // TODO: this may not be safe!? 3756 flatbuf_len = (flatbuf_len + 7) & ~7; 3757 3758 //assert(NdbProfileRecord_verify_as_root(flatbuf, flatbuf_len) == 0); 3759 3760 // get dbs 3761 profile_db = txn->lmdb->dbs[NDB_DB_PROFILE]; 3762 3763 // get new key 3764 profile_key = ndb_get_last_key(txn->mdb_txn, profile_db) + 1; 3765 3766 // write profile to profile store 3767 key.mv_data = &profile_key; 3768 key.mv_size = sizeof(profile_key); 3769 val.mv_data = flatbuf; 3770 val.mv_size = flatbuf_len; 3771 3772 if ((rc = mdb_put(txn->mdb_txn, profile_db, &key, &val, 0))) { 3773 ndb_debug("write profile to db failed: %s\n", mdb_strerror(rc)); 3774 return 0; 3775 } 3776 3777 // write last fetched record 3778 if (!ndb_maybe_write_last_profile_fetch(txn, note)) { 3779 ndb_debug("failed to write last profile fetched record\n"); 3780 } 3781 3782 // write profile pubkey index 3783 if (!ndb_write_profile_pk_index(txn, note, profile_key)) { 3784 ndb_debug("failed to write profile pubkey index\n"); 3785 return 0; 3786 } 3787 3788 // write name, display_name profile search indices 3789 if (!ndb_write_profile_search_indices(txn, note, profile_key, 3790 flatbuf)) { 3791 ndb_debug("failed to write profile search indices\n"); 3792 return 0; 3793 } 3794 3795 return 1; 3796 } 3797 3798 int ndb_set_note_meta(struct ndb *ndb, const unsigned char *id, struct ndb_note_meta *meta) 3799 { 3800 struct ndb_writer_msg msg; 3801 struct ndb_writer_note_meta *meta_msg = &msg.note_meta; 3802 3803 msg.type = NDB_WRITER_NOTE_META; 3804 3805 memcpy(meta_msg->note_id, id, 32); 3806 meta_msg->metadata = meta; 3807 3808 return ndb_writer_queue_msg(&ndb->writer, &msg); 3809 } 3810 3811 int ndb_writer_set_note_meta(struct ndb_txn *txn, const unsigned char *id, struct ndb_note_meta *meta) 3812 { 3813 int rc; 3814 MDB_val k, v; 3815 MDB_dbi note_meta_db; 3816 3817 // get dbs 3818 note_meta_db = txn->lmdb->dbs[NDB_DB_META]; 3819 3820 k.mv_data = (unsigned char *)id; 3821 k.mv_size = 32; 3822 3823 v.mv_data = (unsigned char *)meta; 3824 v.mv_size = ndb_note_meta_total_size(meta); 3825 3826 if ((rc = mdb_put(txn->mdb_txn, note_meta_db, &k, &v, 0))) { 3827 ndb_debug("ndb_set_note_meta: write note metadata to db failed: %s\n", mdb_strerror(rc)); 3828 return 0; 3829 } 3830 3831 return 1; 3832 } 3833 3834 struct ndb_note_meta *ndb_get_note_meta(struct ndb_txn *txn, const unsigned char *id) 3835 { 3836 MDB_val k, v; 3837 3838 k.mv_data = (unsigned char*)id; 3839 k.mv_size = 32; 3840 3841 if (mdb_get(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &k, &v)) { 3842 ndb_debug("ndb_get_note_meta: mdb_get note failed\n"); 3843 return NULL; 3844 } 3845 3846 return v.mv_data; 3847 } 3848 3849 /* write reaction stats if its a valid reaction */ 3850 static int ndb_process_reaction( 3851 struct ndb_txn *txn, 3852 struct ndb_note *note, 3853 unsigned char **liked, 3854 unsigned char *scratch, 3855 size_t scratch_size) 3856 { 3857 const char *content; 3858 int rc; 3859 uint32_t *count; 3860 MDB_val key, val; 3861 union ndb_reaction_str reaction_str; 3862 struct ndb_note_meta *meta; 3863 struct ndb_note_meta_entry *entry; 3864 enum ndb_meta_clone_result cres; 3865 char strbuf[128]; 3866 3867 *liked = ndb_note_last_id_tag(note, 'e'); 3868 3869 if (*liked == NULL) 3870 return 0; 3871 3872 meta = ndb_get_note_meta(txn, *liked); 3873 3874 /* initial builder setup, build reaction string from reaction contents */ 3875 content = ndb_note_content(note); 3876 if (!ndb_reaction_set(&reaction_str, content)) { 3877 ndb_debug("reaction string '%s' was too big\n", content); 3878 /* string was too big, let's just record a `+` for now */ 3879 rc = ndb_reaction_set(&reaction_str, "+"); 3880 assert(rc); 3881 } 3882 3883 cres = ndb_note_meta_clone_with_entry(&meta, 3884 &entry, 3885 NDB_NOTE_META_REACTION, 3886 &reaction_str.binmoji, 3887 scratch, 3888 scratch_size); 3889 3890 switch (cres) { 3891 case NDB_META_CLONE_FAILED: 3892 return 0; 3893 case NDB_META_CLONE_NEW_ENTRY: 3894 ndb_reaction_to_str(&reaction_str, strbuf); 3895 /* printf("initializing reaction stats for %s\n", strbuf); */ 3896 ndb_note_meta_reaction_set(entry, 1, reaction_str); 3897 break; 3898 case NDB_META_CLONE_EXISTING_ENTRY: 3899 count = ndb_note_meta_reaction_count(entry); 3900 /* printf("increasing count from %d to %d\n", (int)*count, (int)*count+1); */ 3901 (*count)++; 3902 break; 3903 } 3904 3905 key.mv_data = *liked; 3906 key.mv_size = 32; 3907 3908 val.mv_data = meta; 3909 val.mv_size = ndb_note_meta_total_size(meta); 3910 assert((val.mv_size % 8) == 0); 3911 3912 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { 3913 ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); 3914 return 0; 3915 } 3916 3917 return 1; 3918 } 3919 3920 static int ndb_increment_total_reactions( 3921 struct ndb_txn *txn, 3922 unsigned char *liked, 3923 unsigned char *scratch, 3924 size_t scratch_size) 3925 { 3926 MDB_val key, val; 3927 uint32_t *total_reactions; 3928 struct ndb_note_meta *meta; 3929 struct ndb_note_meta_entry *entry; 3930 int rc; 3931 3932 meta = ndb_get_note_meta(txn, liked); 3933 rc = ndb_note_meta_clone_with_entry(&meta, &entry, 3934 NDB_NOTE_META_COUNTS, 3935 NULL, /* payload to match. only relevant for reactions */ 3936 scratch, 3937 scratch_size); 3938 3939 switch (rc) { 3940 case NDB_META_CLONE_FAILED: 3941 return 0; 3942 case NDB_META_CLONE_NEW_ENTRY: 3943 ndb_note_meta_counts_set(entry, 1, 0, 0, 0, 0); 3944 break; 3945 case NDB_META_CLONE_EXISTING_ENTRY: 3946 total_reactions = ndb_note_meta_counts_total_reactions(entry); 3947 (*total_reactions)++; 3948 break; 3949 } 3950 3951 key.mv_data = liked; 3952 key.mv_size = 32; 3953 3954 val.mv_data = meta; 3955 val.mv_size = ndb_note_meta_total_size(meta); 3956 assert((val.mv_size % 8) == 0); 3957 3958 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { 3959 ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); 3960 return 0; 3961 } 3962 3963 return 1; 3964 } 3965 3966 3967 // When receiving a reaction note, look for the liked id and increase the 3968 // reaction counter in the note metadata database 3969 static int ndb_write_reaction_stats(struct ndb_txn *txn, struct ndb_note *note, 3970 unsigned char *scratch, 3971 size_t scratch_size) 3972 { 3973 unsigned char *liked; 3974 /* we short circuit here since we only want to increment total reaction count 3975 * if its a valid reaction */ 3976 return ndb_process_reaction(txn, note, &liked, scratch, scratch_size) && 3977 ndb_increment_total_reactions(txn, liked, scratch, scratch_size); 3978 } 3979 3980 3981 static int ndb_write_note_id_index(struct ndb_txn *txn, struct ndb_note *note, 3982 uint64_t note_key) 3983 3984 { 3985 struct ndb_tsid tsid; 3986 int rc; 3987 MDB_val key, val; 3988 MDB_dbi id_db; 3989 3990 ndb_tsid_init(&tsid, note->id, note->created_at); 3991 3992 key.mv_data = &tsid; 3993 key.mv_size = sizeof(tsid); 3994 val.mv_data = ¬e_key; 3995 val.mv_size = sizeof(note_key); 3996 3997 id_db = txn->lmdb->dbs[NDB_DB_NOTE_ID]; 3998 3999 if ((rc = mdb_put(txn->mdb_txn, id_db, &key, &val, 0))) { 4000 ndb_debug("write note id index to db failed: %s\n", 4001 mdb_strerror(rc)); 4002 return 0; 4003 } 4004 4005 return 1; 4006 } 4007 4008 static int ndb_filter_group_add_filters(struct ndb_filter_group *group, 4009 struct ndb_filter *filters, 4010 int num_filters) 4011 { 4012 int i; 4013 4014 for (i = 0; i < num_filters; i++) { 4015 if (!ndb_filter_group_add(group, &filters[i])) 4016 return 0; 4017 } 4018 4019 return 1; 4020 } 4021 4022 4023 static struct ndb_filter_elements * 4024 ndb_filter_find_elements(struct ndb_filter *filter, enum ndb_filter_fieldtype typ) 4025 { 4026 int i; 4027 struct ndb_filter_elements *els; 4028 4029 for (i = 0; i < filter->num_elements; i++) { 4030 els = ndb_filter_get_elements(filter, i); 4031 assert(els); 4032 if (els->field.type == typ) { 4033 return els; 4034 } 4035 } 4036 4037 return NULL; 4038 } 4039 4040 static const char *ndb_filter_find_search(struct ndb_filter *filter) 4041 { 4042 struct ndb_filter_elements *els; 4043 4044 if (!(els = ndb_filter_find_elements(filter, NDB_FILTER_SEARCH))) 4045 return NULL; 4046 4047 return ndb_filter_get_string_element(filter, els, 0); 4048 } 4049 4050 int ndb_filter_is_subset_of(const struct ndb_filter *a, const struct ndb_filter *b) 4051 { 4052 int i; 4053 struct ndb_filter_elements *b_field, *a_field; 4054 4055 // Everything is always a subset of {} 4056 if (b->num_elements == 0) 4057 return 1; 4058 4059 // We can't be a subset if the number of elements in the other 4060 // filter is larger then the number of elements we have. 4061 if (b->num_elements > a->num_elements) 4062 return 0; 4063 4064 // If our filter count matches, we can only be a subset if we are 4065 // equal 4066 if (b->num_elements == a->num_elements) 4067 return ndb_filter_eq(a, b); 4068 4069 // If our element count is larger than the other filter, then we 4070 // must see if every element in the other filter exists in ours. If 4071 // so, then we are a subset of the other. 4072 // 4073 // eg: B={k:1, a:b} <- A={t:x, k:1, a:b} 4074 // 4075 // A is a subset of B because `k:1` and `a:b` both exist in A 4076 4077 for (i = 0; i < b->num_elements; i++) { 4078 b_field = ndb_filter_get_elements((struct ndb_filter*)b, i); 4079 a_field = ndb_filter_find_elements((struct ndb_filter*)a, 4080 b_field->field.type); 4081 4082 if (a_field == NULL) 4083 return 0; 4084 4085 if (!ndb_filter_field_eq((struct ndb_filter*)a, a_field, 4086 (struct ndb_filter*)b, b_field)) 4087 return 0; 4088 } 4089 4090 return 1; 4091 } 4092 4093 int ndb_filter_eq(const struct ndb_filter *a, const struct ndb_filter *b) 4094 { 4095 int i; 4096 struct ndb_filter_elements *a_els, *b_els; 4097 4098 if (a->num_elements != b->num_elements) 4099 return 0; 4100 4101 for (i = 0; i < a->num_elements; i++) { 4102 a_els = ndb_filter_get_elements((struct ndb_filter*)a, i); 4103 b_els = ndb_filter_find_elements((struct ndb_filter *)b, 4104 a_els->field.type); 4105 4106 if (b_els == NULL) 4107 return 0; 4108 4109 if (!ndb_filter_field_eq((struct ndb_filter*)a, a_els, 4110 (struct ndb_filter*)b, b_els)) 4111 return 0; 4112 } 4113 4114 return 1; 4115 } 4116 4117 4118 static uint64_t * 4119 ndb_filter_get_elem(struct ndb_filter *filter, enum ndb_filter_fieldtype typ) 4120 { 4121 struct ndb_filter_elements *els; 4122 if ((els = ndb_filter_find_elements(filter, typ))) 4123 return &els->elements[0]; 4124 return NULL; 4125 } 4126 4127 static uint64_t *ndb_filter_get_int(struct ndb_filter *filter, 4128 enum ndb_filter_fieldtype typ) 4129 { 4130 uint64_t *el; 4131 if (NULL == (el = ndb_filter_get_elem(filter, typ))) 4132 return NULL; 4133 return el; 4134 } 4135 4136 static inline int push_query_result(struct ndb_query_results *results, 4137 struct ndb_query_result *result) 4138 { 4139 return cursor_push(&results->cur, (unsigned char*)result, sizeof(*result)); 4140 } 4141 4142 static int compare_query_results(const void *pa, const void *pb) 4143 { 4144 struct ndb_query_result *a, *b; 4145 4146 a = (struct ndb_query_result *)pa; 4147 b = (struct ndb_query_result *)pb; 4148 4149 if (a->note->created_at == b->note->created_at) { 4150 return 0; 4151 } else if (a->note->created_at > b->note->created_at) { 4152 return -1; 4153 } else { 4154 return 1; 4155 } 4156 } 4157 4158 static void ndb_query_result_init(struct ndb_query_result *res, 4159 struct ndb_note *note, 4160 uint64_t note_size, 4161 uint64_t note_id) 4162 { 4163 *res = (struct ndb_query_result){ 4164 .note_id = note_id, 4165 .note_size = note_size, 4166 .note = note, 4167 }; 4168 } 4169 4170 static int query_is_full(struct ndb_query_results *results, int limit) 4171 { 4172 if (results->cur.p >= results->cur.end) 4173 return 1; 4174 4175 return cursor_count(&results->cur, sizeof(struct ndb_query_result)) >= limit; 4176 } 4177 4178 static int ndb_query_plan_execute_search(struct ndb_txn *txn, 4179 struct ndb_filter *filter, 4180 struct ndb_query_results *results, 4181 int limit) 4182 { 4183 const char *search; 4184 int i; 4185 struct ndb_text_search_results text_results; 4186 struct ndb_text_search_result *text_result; 4187 struct ndb_text_search_config config; 4188 struct ndb_query_result result; 4189 4190 ndb_default_text_search_config(&config); 4191 4192 if (!(search = ndb_filter_find_search(filter))) 4193 return 0; 4194 4195 if (!ndb_text_search_with(txn, search, &text_results, &config, filter)) 4196 return 0; 4197 4198 for (i = 0; i < text_results.num_results; i++) { 4199 if (query_is_full(results, limit)) 4200 break; 4201 4202 text_result = &text_results.results[i]; 4203 4204 result.note = text_result->note; 4205 result.note_size = text_result->note_size; 4206 result.note_id = text_result->key.note_id; 4207 4208 if (!push_query_result(results, &result)) 4209 break; 4210 } 4211 4212 return 1; 4213 } 4214 4215 static int ndb_query_plan_execute_ids(struct ndb_txn *txn, 4216 struct ndb_filter *filter, 4217 struct ndb_query_results *results, 4218 int limit) 4219 { 4220 MDB_cursor *cur; 4221 MDB_dbi db; 4222 MDB_val k, v; 4223 int rc, i, need_relays = 0; 4224 struct ndb_filter_elements *ids; 4225 struct ndb_note *note; 4226 struct ndb_query_result res; 4227 struct ndb_tsid tsid, *ptsid; 4228 uint64_t note_id, until, *pint; 4229 size_t note_size; 4230 unsigned char *id; 4231 struct ndb_note_relay_iterator note_relay_iter = {0}; 4232 struct ndb_note_relay_iterator *relay_iter = NULL; 4233 4234 until = UINT64_MAX; 4235 4236 if (!(ids = ndb_filter_find_elements(filter, NDB_FILTER_IDS))) 4237 return 0; 4238 4239 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4240 until = *pint; 4241 4242 if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS)) 4243 need_relays = 1; 4244 4245 db = txn->lmdb->dbs[NDB_DB_NOTE_ID]; 4246 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4247 return 0; 4248 4249 // for each id in our ids filter, find in the db 4250 for (i = 0; i < ids->count; i++) { 4251 if (query_is_full(results, limit)) 4252 break; 4253 4254 id = ndb_filter_get_id_element(filter, ids, i); 4255 ndb_tsid_init(&tsid, (unsigned char *)id, until); 4256 4257 k.mv_data = &tsid; 4258 k.mv_size = sizeof(tsid); 4259 4260 if (!ndb_cursor_start(cur, &k, &v)) 4261 continue; 4262 4263 ptsid = (struct ndb_tsid *)k.mv_data; 4264 note_id = *(uint64_t*)v.mv_data; 4265 4266 if (memcmp(id, ptsid->id, 32)) 4267 continue; 4268 4269 // get the note because we need it to match against the filter 4270 if (!(note = ndb_get_note_by_key(txn, note_id, ¬e_size))) 4271 continue; 4272 4273 relay_iter = need_relays ? ¬e_relay_iter : NULL; 4274 if (relay_iter) 4275 ndb_note_relay_iterate_start(txn, relay_iter, note_id); 4276 4277 // Sure this particular lookup matched the index query, but 4278 // does it match the entire filter? Check! We also pass in 4279 // things we've already matched via the filter so we don't have 4280 // to check again. This can be pretty important for filters 4281 // with a large number of entries. 4282 if (!ndb_filter_matches_with(filter, note, 1 << NDB_FILTER_IDS, relay_iter)) { 4283 ndb_note_relay_iterate_close(relay_iter); 4284 continue; 4285 } 4286 ndb_note_relay_iterate_close(relay_iter); 4287 4288 ndb_query_result_init(&res, note, note_size, note_id); 4289 if (!push_query_result(results, &res)) 4290 break; 4291 } 4292 4293 mdb_cursor_close(cur); 4294 return 1; 4295 } 4296 4297 // 4298 // encode a tag index key 4299 // 4300 // consists of: 4301 // 4302 // u8 tag 4303 // u8 tag_val_len 4304 // [u8] tag_val_bytes 4305 // u64 created_at 4306 // 4307 static int ndb_encode_tag_key(unsigned char *buf, int buf_size, 4308 char tag, const unsigned char *val, 4309 unsigned char val_len, 4310 uint64_t timestamp) 4311 { 4312 struct cursor writer; 4313 int ok; 4314 4315 // quick exit for obvious case where it will be too big. There can be 4316 // values of val_len that still fail, but we just let the writer handle 4317 // those failure cases 4318 if (val_len >= buf_size) 4319 return 0; 4320 4321 make_cursor(buf, buf + buf_size, &writer); 4322 4323 ok = 4324 cursor_push_byte(&writer, tag) && 4325 cursor_push(&writer, (unsigned char*)val, val_len) && 4326 cursor_push(&writer, (unsigned char*)×tamp, sizeof(timestamp)); 4327 4328 if (!ok) 4329 return 0; 4330 4331 return writer.p - writer.start; 4332 } 4333 4334 static int ndb_query_plan_execute_authors(struct ndb_txn *txn, 4335 struct ndb_filter *filter, 4336 struct ndb_query_results *results, 4337 int limit) 4338 { 4339 MDB_val k, v; 4340 MDB_cursor *cur; 4341 int rc, i, need_relays = 0; 4342 uint64_t *pint, until, since, note_key; 4343 unsigned char *author; 4344 struct ndb_note *note; 4345 size_t note_size; 4346 struct ndb_filter_elements *authors; 4347 struct ndb_query_result res; 4348 struct ndb_tsid tsid, *ptsid; 4349 struct ndb_note_relay_iterator note_relay_iter; 4350 enum ndb_dbs db; 4351 4352 db = txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY]; 4353 4354 if (!(authors = ndb_filter_find_elements(filter, NDB_FILTER_AUTHORS))) 4355 return 0; 4356 4357 until = UINT64_MAX; 4358 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4359 until = *pint; 4360 4361 since = 0; 4362 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE))) 4363 since = *pint; 4364 4365 if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS)) 4366 need_relays = 1; 4367 4368 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4369 return 0; 4370 4371 for (i = 0; i < authors->count; i++) { 4372 author = ndb_filter_get_id_element(filter, authors, i); 4373 4374 ndb_tsid_init(&tsid, author, until); 4375 4376 k.mv_data = &tsid; 4377 k.mv_size = sizeof(tsid); 4378 4379 if (!ndb_cursor_start(cur, &k, &v)) 4380 continue; 4381 4382 // for each id in our ids filter, find in the db 4383 while (!query_is_full(results, limit)) { 4384 ptsid = (struct ndb_tsid *)k.mv_data; 4385 note_key = *(uint64_t*)v.mv_data; 4386 4387 // don't continue the scan if we're below `since` 4388 if (ptsid->timestamp < since) 4389 break; 4390 4391 // our author should match, if not bail 4392 if (memcmp(author, ptsid->id, 32)) 4393 break; 4394 4395 // fetch the note, we need it for our query results 4396 // and to match further against the filter 4397 if (!(note = ndb_get_note_by_key(txn, note_key, ¬e_size))) 4398 goto next; 4399 4400 if (need_relays) 4401 ndb_note_relay_iterate_start(txn, ¬e_relay_iter, note_key); 4402 4403 if (!ndb_filter_matches_with(filter, note, 4404 1 << NDB_FILTER_AUTHORS, 4405 need_relays ? ¬e_relay_iter : NULL)) 4406 { 4407 goto next; 4408 } 4409 4410 ndb_query_result_init(&res, note, note_size, note_key); 4411 if (!push_query_result(results, &res)) 4412 break; 4413 4414 next: 4415 if (mdb_cursor_get(cur, &k, &v, MDB_PREV)) 4416 break; 4417 } 4418 } 4419 4420 mdb_cursor_close(cur); 4421 return 1; 4422 } 4423 4424 static int ndb_query_plan_execute_created_at(struct ndb_txn *txn, 4425 struct ndb_filter *filter, 4426 struct ndb_query_results *results, 4427 int limit) 4428 { 4429 MDB_dbi db; 4430 MDB_val k, v; 4431 MDB_cursor *cur; 4432 int rc, need_relays = 0; 4433 struct ndb_note *note; 4434 struct ndb_tsid key, *pkey; 4435 uint64_t *pint, until, since, note_id; 4436 size_t note_size; 4437 struct ndb_query_result res; 4438 struct ndb_note_relay_iterator note_relay_iter; 4439 unsigned char high_key[32] = {0xFF}; 4440 4441 db = txn->lmdb->dbs[NDB_DB_NOTE_ID]; 4442 4443 until = UINT64_MAX; 4444 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4445 until = *pint; 4446 4447 since = 0; 4448 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE))) 4449 since = *pint; 4450 4451 if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS)) 4452 need_relays = 1; 4453 4454 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4455 return 0; 4456 4457 // if we have until, start there, otherwise just use max 4458 ndb_tsid_init(&key, high_key, until); 4459 k.mv_data = &key; 4460 k.mv_size = sizeof(key); 4461 4462 if (!ndb_cursor_start(cur, &k, &v)) 4463 return 1; 4464 4465 while (!query_is_full(results, limit)) { 4466 pkey = (struct ndb_tsid *)k.mv_data; 4467 note_id = *(uint64_t*)v.mv_data; 4468 assert(v.mv_size == 8); 4469 4470 // don't continue the scan if we're below `since` 4471 if (pkey->timestamp < since) 4472 break; 4473 4474 if (!(note = ndb_get_note_by_key(txn, note_id, ¬e_size))) 4475 goto next; 4476 4477 if (need_relays) 4478 ndb_note_relay_iterate_start(txn, ¬e_relay_iter, note_id); 4479 4480 // does this entry match our filter? 4481 if (!ndb_filter_matches_with(filter, note, 0, need_relays ? ¬e_relay_iter : NULL)) 4482 goto next; 4483 4484 ndb_query_result_init(&res, note, (uint64_t)note_size, note_id); 4485 if (!push_query_result(results, &res)) 4486 break; 4487 next: 4488 if (mdb_cursor_get(cur, &k, &v, MDB_PREV)) 4489 break; 4490 } 4491 4492 mdb_cursor_close(cur); 4493 return 1; 4494 } 4495 4496 static int ndb_query_plan_execute_tags(struct ndb_txn *txn, 4497 struct ndb_filter *filter, 4498 struct ndb_query_results *results, 4499 int limit) 4500 { 4501 MDB_cursor *cur; 4502 MDB_dbi db; 4503 MDB_val k, v; 4504 int len, taglen, rc, i, need_relays = 0; 4505 uint64_t *pint, until, note_id; 4506 size_t note_size; 4507 unsigned char key_buffer[255]; 4508 struct ndb_note *note; 4509 struct ndb_filter_elements *tags; 4510 unsigned char *tag; 4511 struct ndb_query_result res; 4512 struct ndb_note_relay_iterator note_relay_iter; 4513 4514 db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; 4515 4516 if (!(tags = ndb_filter_find_elements(filter, NDB_FILTER_TAGS))) 4517 return 0; 4518 4519 if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS)) 4520 need_relays = 1; 4521 4522 until = UINT64_MAX; 4523 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4524 until = *pint; 4525 4526 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4527 return 0; 4528 4529 for (i = 0; i < tags->count; i++) { 4530 tag = ndb_filter_get_id_element(filter, tags, i); 4531 4532 taglen = tags->field.elem_type == NDB_ELEMENT_ID 4533 ? 32 : strlen((const char*)tag); 4534 4535 if (!(len = ndb_encode_tag_key(key_buffer, sizeof(key_buffer), 4536 tags->field.tag, tag, taglen, 4537 until))) { 4538 goto fail; 4539 } 4540 4541 k.mv_data = key_buffer; 4542 k.mv_size = len; 4543 4544 if (!ndb_cursor_start(cur, &k, &v)) 4545 continue; 4546 4547 // for each id in our ids filter, find in the db 4548 while (!query_is_full(results, limit)) { 4549 // check if tag value matches, bail if not 4550 if (((unsigned char *)k.mv_data)[0] != tags->field.tag) 4551 break; 4552 4553 // check if tag value matches, bail if not 4554 if (taglen != k.mv_size - 9) 4555 break; 4556 4557 if (memcmp((unsigned char *)k.mv_data+1, tag, k.mv_size-9)) 4558 break; 4559 4560 note_id = *(uint64_t*)v.mv_data; 4561 4562 if (!(note = ndb_get_note_by_key(txn, note_id, ¬e_size))) 4563 goto next; 4564 4565 if (need_relays) 4566 ndb_note_relay_iterate_start(txn, ¬e_relay_iter, note_id); 4567 4568 if (!ndb_filter_matches_with(filter, note, 4569 1 << NDB_FILTER_TAGS, 4570 need_relays ? ¬e_relay_iter : NULL)) 4571 goto next; 4572 4573 ndb_query_result_init(&res, note, note_size, note_id); 4574 if (!push_query_result(results, &res)) 4575 break; 4576 4577 next: 4578 if (mdb_cursor_get(cur, &k, &v, MDB_PREV)) 4579 break; 4580 } 4581 } 4582 4583 mdb_cursor_close(cur); 4584 return 1; 4585 fail: 4586 mdb_cursor_close(cur); 4587 return 0; 4588 } 4589 4590 static int ndb_query_plan_execute_author_kinds( 4591 struct ndb_txn *txn, 4592 struct ndb_filter *filter, 4593 struct ndb_query_results *results, 4594 int limit) 4595 { 4596 MDB_cursor *cur; 4597 MDB_dbi db; 4598 MDB_val k, v; 4599 struct ndb_note *note; 4600 struct ndb_filter_elements *kinds, *relays, *authors; 4601 struct ndb_query_result res; 4602 uint64_t kind, note_id, until, since, *pint; 4603 size_t note_size; 4604 unsigned char *author; 4605 int i, j, rc; 4606 struct ndb_id_u64_ts key, *pkey; 4607 struct ndb_note_relay_iterator note_relay_iter; 4608 4609 // we should have kinds in a kinds filter! 4610 if (!(kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS))) 4611 return 0; 4612 // 4613 // we should have kinds in a kinds filter! 4614 if (!(authors = ndb_filter_find_elements(filter, NDB_FILTER_AUTHORS))) 4615 return 0; 4616 4617 relays = ndb_filter_find_elements(filter, NDB_FILTER_RELAYS); 4618 4619 until = UINT64_MAX; 4620 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4621 until = *pint; 4622 4623 since = 0; 4624 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE))) 4625 since = *pint; 4626 4627 db = txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND]; 4628 4629 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4630 return 0; 4631 4632 for (j = 0; j < authors->count; j++) { 4633 if (query_is_full(results, limit)) 4634 break; 4635 4636 if (!(author = ndb_filter_get_id_element(filter, authors, j))) 4637 continue; 4638 4639 for (i = 0; i < kinds->count; i++) { 4640 if (query_is_full(results, limit)) 4641 break; 4642 4643 kind = kinds->elements[i]; 4644 4645 ndb_debug("finding kind %"PRIu64"\n", kind); 4646 4647 ndb_id_u64_ts_init(&key, author, kind, until); 4648 4649 k.mv_data = &key; 4650 k.mv_size = sizeof(key); 4651 4652 if (!ndb_cursor_start(cur, &k, &v)) 4653 continue; 4654 4655 // scan the kind subindex 4656 while (!query_is_full(results, limit)) { 4657 pkey = (struct ndb_id_u64_ts*)k.mv_data; 4658 4659 ndb_debug("scanning subindex kind:%"PRIu64" created_at:%"PRIu64" pubkey:", 4660 pkey->u64, 4661 pkey->timestamp); 4662 4663 if (pkey->u64 != kind) 4664 break; 4665 4666 // don't continue the scan if we're below `since` 4667 if (pkey->timestamp < since) 4668 break; 4669 4670 if (memcmp(pkey->id, author, 32)) 4671 break; 4672 4673 note_id = *(uint64_t*)v.mv_data; 4674 if (!(note = ndb_get_note_by_key(txn, note_id, ¬e_size))) 4675 goto next; 4676 4677 if (relays) 4678 ndb_note_relay_iterate_start(txn, ¬e_relay_iter, note_id); 4679 4680 if (!ndb_filter_matches_with(filter, note, 4681 (1 << NDB_FILTER_KINDS) | (1 << NDB_FILTER_AUTHORS), 4682 relays? ¬e_relay_iter : NULL)) 4683 goto next; 4684 4685 ndb_query_result_init(&res, note, note_size, note_id); 4686 if (!push_query_result(results, &res)) 4687 break; 4688 4689 next: 4690 if (mdb_cursor_get(cur, &k, &v, MDB_PREV)) 4691 break; 4692 } 4693 } 4694 } 4695 4696 mdb_cursor_close(cur); 4697 return 1; 4698 } 4699 4700 static int ndb_query_plan_execute_profile_search( 4701 struct ndb_txn *txn, 4702 struct ndb_filter *filter, 4703 struct ndb_query_results *results, 4704 int limit) 4705 { 4706 const char *search; 4707 int i; 4708 4709 // The filter pubkey is updated inplace for each note search 4710 unsigned char *filter_pubkey; 4711 unsigned char pubkey[32] = {0}; 4712 struct ndb_filter_elements *els; 4713 struct ndb_search profile_search; 4714 struct ndb_filter note_filter, *f = ¬e_filter; 4715 4716 if (!(search = ndb_filter_find_search(filter))) 4717 return 0; 4718 4719 if (!ndb_filter_init_with(f, 1)) 4720 return 0; 4721 4722 ndb_filter_start_field(f, NDB_FILTER_KINDS); 4723 ndb_filter_add_int_element(f, 0); 4724 ndb_filter_end_field(f); 4725 4726 ndb_filter_start_field(f, NDB_FILTER_AUTHORS); 4727 ndb_filter_add_id_element(f, pubkey); 4728 ndb_filter_end_field(f); 4729 ndb_filter_end(f); 4730 4731 // get the authors element after we finalize the filter, since 4732 // the data could have moved 4733 if (!(els = ndb_filter_find_elements(f, NDB_FILTER_AUTHORS))) 4734 goto fail; 4735 4736 // grab pointer to pubkey in the filter so that we can 4737 // update the filter as we go 4738 if (!(filter_pubkey = ndb_filter_get_id_element(f, els, 0))) 4739 goto fail; 4740 4741 for (i = 0; !query_is_full(results, limit); i++) { 4742 if (i == 0) { 4743 if (!ndb_search_profile(txn, &profile_search, search)) 4744 break; 4745 } else { 4746 if (!ndb_search_profile_next(&profile_search)) 4747 break; 4748 } 4749 4750 // Copy pubkey into filter 4751 memcpy(filter_pubkey, profile_search.key->id, 32); 4752 4753 // Look up the corresponding note associated with that pubkey 4754 if (!ndb_query_plan_execute_author_kinds(txn, f, results, limit)) 4755 goto fail; 4756 } 4757 4758 ndb_search_profile_end(&profile_search); 4759 ndb_filter_destroy(f); 4760 return 1; 4761 4762 fail: 4763 ndb_filter_destroy(f); 4764 return 0; 4765 } 4766 4767 static int ndb_query_plan_execute_relay_kinds( 4768 struct ndb_txn *txn, 4769 struct ndb_filter *filter, 4770 struct ndb_query_results *results, 4771 int limit) 4772 { 4773 MDB_cursor *cur; 4774 MDB_dbi db; 4775 MDB_val k, v; 4776 struct ndb_note *note; 4777 struct ndb_filter_elements *kinds, *relays; 4778 struct ndb_query_result res; 4779 uint64_t kind, note_id, until, since, *pint; 4780 size_t note_size; 4781 const char *relay; 4782 int i, j, rc, len; 4783 struct ndb_relay_kind_key relay_key; 4784 unsigned char keybuf[256]; 4785 4786 // we should have kinds in a kinds filter! 4787 if (!(kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS))) 4788 return 0; 4789 4790 if (!(relays = ndb_filter_find_elements(filter, NDB_FILTER_RELAYS))) 4791 return 0; 4792 4793 until = UINT64_MAX; 4794 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4795 until = *pint; 4796 4797 since = 0; 4798 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE))) 4799 since = *pint; 4800 4801 db = txn->lmdb->dbs[NDB_DB_NOTE_RELAY_KIND]; 4802 4803 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4804 return 0; 4805 4806 for (j = 0; j < relays->count; j++) { 4807 if (query_is_full(results, limit)) 4808 break; 4809 4810 if (!(relay = ndb_filter_get_string_element(filter, relays, j))) 4811 continue; 4812 4813 for (i = 0; i < kinds->count; i++) { 4814 if (query_is_full(results, limit)) 4815 break; 4816 4817 kind = kinds->elements[i]; 4818 ndb_debug("kind %" PRIu64 "\n", kind); 4819 4820 if (!ndb_relay_kind_key_init_high(&relay_key, relay, kind, until)) { 4821 ndb_debug("ndb_relay_kind_key_init_high failed in relay query\n"); 4822 continue; 4823 } 4824 4825 if (!(len = ndb_build_relay_kind_key(keybuf, sizeof(keybuf), &relay_key))) { 4826 ndb_debug("ndb_build_relay_kind_key failed in relay query\n"); 4827 ndb_debug_relay_kind_key(&relay_key); 4828 continue; 4829 } 4830 4831 k.mv_data = keybuf; 4832 k.mv_size = len; 4833 4834 ndb_debug("starting with key "); 4835 ndb_debug_relay_kind_key(&relay_key); 4836 4837 if (!ndb_cursor_start(cur, &k, &v)) 4838 continue; 4839 4840 // scan the kind subindex 4841 while (!query_is_full(results, limit)) { 4842 ndb_parse_relay_kind_key(&relay_key, k.mv_data); 4843 4844 ndb_debug("inside kind subindex "); 4845 ndb_debug_relay_kind_key(&relay_key); 4846 4847 if (relay_key.kind != kind) 4848 break; 4849 4850 if (strcmp(relay_key.relay, relay)) 4851 break; 4852 4853 // don't continue the scan if we're below `since` 4854 if (relay_key.created_at < since) 4855 break; 4856 4857 note_id = relay_key.note_key; 4858 if (!(note = ndb_get_note_by_key(txn, note_id, ¬e_size))) 4859 goto next; 4860 4861 if (!ndb_filter_matches_with(filter, note, 4862 (1 << NDB_FILTER_KINDS) | (1 << NDB_FILTER_RELAYS), 4863 NULL)) 4864 goto next; 4865 4866 ndb_query_result_init(&res, note, note_size, note_id); 4867 if (!push_query_result(results, &res)) 4868 break; 4869 4870 next: 4871 if (mdb_cursor_get(cur, &k, &v, MDB_PREV)) 4872 break; 4873 } 4874 } 4875 } 4876 4877 mdb_cursor_close(cur); 4878 return 1; 4879 } 4880 4881 static int ndb_query_plan_execute_kinds(struct ndb_txn *txn, 4882 struct ndb_filter *filter, 4883 struct ndb_query_results *results, 4884 int limit) 4885 { 4886 MDB_cursor *cur; 4887 MDB_dbi db; 4888 MDB_val k, v; 4889 struct ndb_note *note; 4890 struct ndb_u64_ts tsid, *ptsid; 4891 struct ndb_filter_elements *kinds; 4892 struct ndb_query_result res; 4893 uint64_t kind, note_id, until, since, *pint; 4894 size_t note_size; 4895 int i, rc, need_relays = 0; 4896 struct ndb_note_relay_iterator note_relay_iter; 4897 4898 // we should have kinds in a kinds filter! 4899 if (!(kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS))) 4900 return 0; 4901 4902 if (ndb_filter_find_elements(filter, NDB_FILTER_RELAYS)) 4903 need_relays = 1; 4904 4905 until = UINT64_MAX; 4906 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 4907 until = *pint; 4908 4909 since = 0; 4910 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE))) 4911 since = *pint; 4912 4913 db = txn->lmdb->dbs[NDB_DB_NOTE_KIND]; 4914 4915 if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) 4916 return 0; 4917 4918 for (i = 0; i < kinds->count; i++) { 4919 if (query_is_full(results, limit)) 4920 break; 4921 4922 kind = kinds->elements[i]; 4923 ndb_debug("kind %" PRIu64 "\n", kind); 4924 ndb_u64_ts_init(&tsid, kind, until); 4925 4926 k.mv_data = &tsid; 4927 k.mv_size = sizeof(tsid); 4928 4929 if (!ndb_cursor_start(cur, &k, &v)) 4930 continue; 4931 4932 // for each id in our ids filter, find in the db 4933 while (!query_is_full(results, limit)) { 4934 ptsid = (struct ndb_u64_ts *)k.mv_data; 4935 if (ptsid->u64 != kind) 4936 break; 4937 4938 // don't continue the scan if we're below `since` 4939 if (ptsid->timestamp < since) 4940 break; 4941 4942 note_id = *(uint64_t*)v.mv_data; 4943 if (!(note = ndb_get_note_by_key(txn, note_id, ¬e_size))) 4944 goto next; 4945 4946 if (need_relays) 4947 ndb_note_relay_iterate_start(txn, ¬e_relay_iter, note_id); 4948 4949 if (!ndb_filter_matches_with(filter, note, 4950 1 << NDB_FILTER_KINDS, 4951 need_relays ? ¬e_relay_iter : NULL)) 4952 goto next; 4953 4954 ndb_query_result_init(&res, note, note_size, note_id); 4955 if (!push_query_result(results, &res)) 4956 break; 4957 4958 next: 4959 if (mdb_cursor_get(cur, &k, &v, MDB_PREV)) 4960 break; 4961 } 4962 } 4963 4964 mdb_cursor_close(cur); 4965 return 1; 4966 } 4967 4968 static enum ndb_query_plan ndb_filter_plan(struct ndb_filter *filter) 4969 { 4970 struct ndb_filter_elements *ids, *kinds, *authors, *tags, *search, *relays; 4971 4972 ids = ndb_filter_find_elements(filter, NDB_FILTER_IDS); 4973 search = ndb_filter_find_elements(filter, NDB_FILTER_SEARCH); 4974 kinds = ndb_filter_find_elements(filter, NDB_FILTER_KINDS); 4975 authors = ndb_filter_find_elements(filter, NDB_FILTER_AUTHORS); 4976 tags = ndb_filter_find_elements(filter, NDB_FILTER_TAGS); 4977 relays = ndb_filter_find_elements(filter, NDB_FILTER_RELAYS); 4978 4979 // profile search 4980 if (kinds && kinds->count == 1 && kinds->elements[0] == 0 && search) { 4981 return NDB_PLAN_PROFILE_SEARCH; 4982 } 4983 4984 // TODO: fix multi-author queries 4985 if (search) { 4986 return NDB_PLAN_SEARCH; 4987 } else if (ids) { 4988 return NDB_PLAN_IDS; 4989 } else if (relays && kinds && !authors) { 4990 return NDB_PLAN_RELAY_KINDS; 4991 } else if (kinds && authors && authors->count == 1) { 4992 return NDB_PLAN_AUTHOR_KINDS; 4993 } else if (authors && authors->count == 1) { 4994 return NDB_PLAN_AUTHORS; 4995 } else if (tags && tags->count == 1) { 4996 return NDB_PLAN_TAGS; 4997 } else if (kinds) { 4998 return NDB_PLAN_KINDS; 4999 } 5000 5001 return NDB_PLAN_CREATED; 5002 } 5003 5004 static const char *ndb_query_plan_name(enum ndb_query_plan plan_id) 5005 { 5006 switch (plan_id) { 5007 case NDB_PLAN_IDS: return "ids"; 5008 case NDB_PLAN_SEARCH: return "search"; 5009 case NDB_PLAN_KINDS: return "kinds"; 5010 case NDB_PLAN_TAGS: return "tags"; 5011 case NDB_PLAN_CREATED: return "created"; 5012 case NDB_PLAN_AUTHORS: return "authors"; 5013 case NDB_PLAN_RELAY_KINDS: return "relay_kinds"; 5014 case NDB_PLAN_AUTHOR_KINDS: return "author_kinds"; 5015 case NDB_PLAN_PROFILE_SEARCH: return "profile_search"; 5016 } 5017 5018 return "unknown"; 5019 } 5020 5021 static int ndb_query_filter(struct ndb_txn *txn, struct ndb_filter *filter, 5022 struct ndb_query_result *res, int capacity, 5023 int *results_out) 5024 { 5025 struct ndb_query_results results; 5026 uint64_t limit, *pint; 5027 enum ndb_query_plan plan; 5028 limit = capacity; 5029 5030 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_LIMIT))) 5031 limit = *pint; 5032 5033 limit = min(capacity, limit); 5034 make_cursor((unsigned char *)res, 5035 ((unsigned char *)res) + limit * sizeof(*res), 5036 &results.cur); 5037 5038 plan = ndb_filter_plan(filter); 5039 ndb_debug("using query plan '%s'\n", ndb_query_plan_name(plan)); 5040 switch (plan) { 5041 // We have a list of ids, just open a cursor and jump to each once 5042 case NDB_PLAN_IDS: 5043 if (!ndb_query_plan_execute_ids(txn, filter, &results, limit)) 5044 return 0; 5045 break; 5046 case NDB_PLAN_RELAY_KINDS: 5047 if (!ndb_query_plan_execute_relay_kinds(txn, filter, &results, limit)) 5048 return 0; 5049 break; 5050 case NDB_PLAN_SEARCH: 5051 if (!ndb_query_plan_execute_search(txn, filter, &results, limit)) 5052 return 0; 5053 break; 5054 5055 case NDB_PLAN_PROFILE_SEARCH: 5056 if (!ndb_query_plan_execute_profile_search(txn, filter, &results, limit)) 5057 return 0; 5058 break; 5059 5060 // We have just kinds, just scan the kind index 5061 case NDB_PLAN_KINDS: 5062 if (!ndb_query_plan_execute_kinds(txn, filter, &results, limit)) 5063 return 0; 5064 break; 5065 case NDB_PLAN_TAGS: 5066 if (!ndb_query_plan_execute_tags(txn, filter, &results, limit)) 5067 return 0; 5068 break; 5069 case NDB_PLAN_CREATED: 5070 if (!ndb_query_plan_execute_created_at(txn, filter, &results, limit)) 5071 return 0; 5072 break; 5073 case NDB_PLAN_AUTHORS: 5074 if (!ndb_query_plan_execute_authors(txn, filter, &results, limit)) 5075 return 0; 5076 break; 5077 case NDB_PLAN_AUTHOR_KINDS: 5078 if (!ndb_query_plan_execute_author_kinds(txn, filter, &results, limit)) 5079 return 0; 5080 break; 5081 } 5082 5083 *results_out = cursor_count(&results.cur, sizeof(*res)); 5084 return 1; 5085 } 5086 5087 int ndb_query(struct ndb_txn *txn, struct ndb_filter *filters, int num_filters, 5088 struct ndb_query_result *results, int result_capacity, int *count) 5089 { 5090 int i, out; 5091 struct ndb_query_result *p = results; 5092 5093 out = 0; 5094 *count = 0; 5095 5096 for (i = 0; i < num_filters; i++) { 5097 if (!ndb_query_filter(txn, &filters[i], p, 5098 result_capacity, &out)) { 5099 return 0; 5100 } 5101 5102 *count += out; 5103 p += out; 5104 result_capacity -= out; 5105 if (result_capacity <= 0) 5106 break; 5107 } 5108 5109 // sort results 5110 qsort(results, *count, sizeof(*results), compare_query_results); 5111 return 1; 5112 } 5113 5114 static int ndb_write_note_tag_index(struct ndb_txn *txn, struct ndb_note *note, 5115 uint64_t note_key) 5116 { 5117 unsigned char key_buffer[255]; 5118 struct ndb_iterator iter; 5119 struct ndb_str tkey, tval; 5120 char tchar; 5121 int len, rc; 5122 MDB_val key, val; 5123 MDB_dbi tags_db; 5124 5125 tags_db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; 5126 5127 ndb_tags_iterate_start(note, &iter); 5128 5129 while (ndb_tags_iterate_next(&iter)) { 5130 if (iter.tag->count < 2) 5131 continue; 5132 5133 tkey = ndb_tag_str(note, iter.tag, 0); 5134 5135 // we only write indices for 1-char tags. 5136 tchar = tkey.str[0]; 5137 if (tchar == 0 || tkey.str[1] != 0) 5138 continue; 5139 5140 tval = ndb_tag_str(note, iter.tag, 1); 5141 len = ndb_str_len(&tval); 5142 5143 if (!(len = ndb_encode_tag_key(key_buffer, sizeof(key_buffer), 5144 tchar, tval.id, (unsigned char)len, 5145 ndb_note_created_at(note)))) { 5146 // this will fail when we try to encode a key that is 5147 // too big 5148 continue; 5149 } 5150 5151 //ndb_debug("writing tag '%c':'data:%d' to index\n", tchar, len); 5152 5153 key.mv_data = key_buffer; 5154 key.mv_size = len; 5155 5156 val.mv_data = ¬e_key; 5157 val.mv_size = sizeof(note_key); 5158 5159 if ((rc = mdb_put(txn->mdb_txn, tags_db, &key, &val, 0))) { 5160 ndb_debug("write note tag index to db failed: %s\n", 5161 mdb_strerror(rc)); 5162 return 0; 5163 } 5164 } 5165 5166 return 1; 5167 } 5168 5169 static int ndb_write_note_kind_index(struct ndb_txn *txn, struct ndb_note *note, 5170 uint64_t note_key) 5171 { 5172 struct ndb_u64_ts tsid; 5173 int rc; 5174 MDB_val key, val; 5175 MDB_dbi kind_db; 5176 5177 ndb_u64_ts_init(&tsid, note->kind, note->created_at); 5178 5179 key.mv_data = &tsid; 5180 key.mv_size = sizeof(tsid); 5181 val.mv_data = ¬e_key; 5182 val.mv_size = sizeof(note_key); 5183 5184 kind_db = txn->lmdb->dbs[NDB_DB_NOTE_KIND]; 5185 5186 if ((rc = mdb_put(txn->mdb_txn, kind_db, &key, &val, 0))) { 5187 ndb_debug("write note kind index to db failed: %s\n", 5188 mdb_strerror(rc)); 5189 return 0; 5190 } 5191 5192 return 1; 5193 } 5194 5195 static int ndb_write_word_to_index(struct ndb_txn *txn, const char *word, 5196 int word_len, int word_index, 5197 uint64_t timestamp, uint64_t note_id) 5198 { 5199 // cap to some reasonable key size 5200 unsigned char buffer[1024]; 5201 int keysize, rc; 5202 MDB_val k, v; 5203 MDB_dbi text_db; 5204 5205 // build our compressed text index key 5206 if (!ndb_make_text_search_key(buffer, sizeof(buffer), word_index, 5207 word_len, word, timestamp, note_id, 5208 &keysize)) { 5209 // probably too big 5210 5211 return 0; 5212 } 5213 5214 k.mv_data = buffer; 5215 k.mv_size = keysize; 5216 5217 v.mv_data = NULL; 5218 v.mv_size = 0; 5219 5220 text_db = txn->lmdb->dbs[NDB_DB_NOTE_TEXT]; 5221 5222 if ((rc = mdb_put(txn->mdb_txn, text_db, &k, &v, 0))) { 5223 ndb_debug("write note text index to db failed: %s\n", 5224 mdb_strerror(rc)); 5225 return 0; 5226 } 5227 5228 return 1; 5229 } 5230 5231 5232 5233 // break a string into individual words for querying or for building the 5234 // fulltext search index. This is callback based so we don't need to 5235 // build up an intermediate structure 5236 static int ndb_parse_words(struct cursor *cur, void *ctx, ndb_word_parser_fn fn) 5237 { 5238 int word_len, words; 5239 const char *word; 5240 5241 words = 0; 5242 5243 while (cur->p < cur->end) { 5244 consume_whitespace_or_punctuation(cur); 5245 if (cur->p >= cur->end) 5246 break; 5247 word = (const char *)cur->p; 5248 5249 if (!consume_until_boundary(cur)) 5250 break; 5251 5252 // start of word or end 5253 word_len = cur->p - (unsigned char *)word; 5254 if (word_len == 0 && cur->p >= cur->end) 5255 break; 5256 5257 if (word_len == 0) { 5258 if (!cursor_skip(cur, 1)) 5259 break; 5260 continue; 5261 } 5262 5263 //ndb_debug("writing word index '%.*s'\n", word_len, word); 5264 5265 if (!fn(ctx, word, word_len, words)) 5266 continue; 5267 5268 words++; 5269 } 5270 5271 return 1; 5272 } 5273 5274 struct ndb_word_writer_ctx 5275 { 5276 struct ndb_txn *txn; 5277 struct ndb_note *note; 5278 uint64_t note_id; 5279 }; 5280 5281 static int ndb_fulltext_word_writer(void *ctx, 5282 const char *word, int word_len, int words) 5283 { 5284 struct ndb_word_writer_ctx *wctx = ctx; 5285 5286 if (!ndb_write_word_to_index(wctx->txn, word, word_len, words, 5287 wctx->note->created_at, wctx->note_id)) { 5288 // too big to write this one, just skip it 5289 ndb_debug("failed to write word '%.*s' to index\n", word_len, word); 5290 5291 return 0; 5292 } 5293 5294 //fprintf(stderr, "wrote '%.*s' to note text index\n", word_len, word); 5295 return 1; 5296 } 5297 5298 static int ndb_write_note_fulltext_index(struct ndb_txn *txn, 5299 struct ndb_note *note, 5300 uint64_t note_id) 5301 { 5302 struct cursor cur; 5303 unsigned char *content; 5304 struct ndb_str str; 5305 struct ndb_word_writer_ctx ctx; 5306 5307 str = ndb_note_str(note, ¬e->content); 5308 // I don't think this should happen? 5309 if (unlikely(str.flag == NDB_PACKED_ID)) 5310 return 0; 5311 5312 content = (unsigned char *)str.str; 5313 5314 make_cursor(content, content + note->content_length, &cur); 5315 5316 ctx.txn = txn; 5317 ctx.note = note; 5318 ctx.note_id = note_id; 5319 5320 ndb_parse_words(&cur, &ctx, ndb_fulltext_word_writer); 5321 5322 return 1; 5323 } 5324 5325 static int ndb_parse_search_words(void *ctx, const char *word_str, int word_len, int word_index) 5326 { 5327 (void)word_index; 5328 struct ndb_search_words *words = ctx; 5329 struct ndb_word *word; 5330 5331 if (words->num_words + 1 > MAX_TEXT_SEARCH_WORDS) 5332 return 0; 5333 5334 word = &words->words[words->num_words++]; 5335 word->word = word_str; 5336 word->word_len = word_len; 5337 5338 return 1; 5339 } 5340 5341 static void ndb_search_words_init(struct ndb_search_words *words) 5342 { 5343 words->num_words = 0; 5344 } 5345 5346 static int prefix_count(const char *str1, int len1, const char *str2, int len2) { 5347 int i, count = 0; 5348 int min_len = len1 < len2 ? len1 : len2; 5349 5350 for (i = 0; i < min_len; i++) { 5351 // case insensitive 5352 if (tolower(str1[i]) == tolower(str2[i])) 5353 count++; 5354 else 5355 break; 5356 } 5357 5358 return count; 5359 } 5360 5361 static int ndb_prefix_matches(struct ndb_text_search_result *result, 5362 struct ndb_word *search_word) 5363 { 5364 // Empty strings shouldn't happen but let's 5365 if (result->key.str_len < 2 || search_word->word_len < 2) 5366 return 0; 5367 5368 // make sure we at least have two matching prefix characters. exact 5369 // matches are nice but range searches allow us to match prefixes as 5370 // well. A double-char prefix is suffient, but maybe we could up this 5371 // in the future. 5372 // 5373 // TODO: How are we handling utf-8 prefix matches like 5374 // japanese? 5375 // 5376 if ( result->key.str[0] != tolower(search_word->word[0]) 5377 && result->key.str[1] != tolower(search_word->word[1]) 5378 ) 5379 return 0; 5380 5381 // count the number of prefix-matched characters. This will be used 5382 // for ranking search results 5383 result->prefix_chars = prefix_count(result->key.str, 5384 result->key.str_len, 5385 search_word->word, 5386 search_word->word_len); 5387 5388 if (result->prefix_chars <= (int)((double)search_word->word_len / 1.5)) 5389 return 0; 5390 5391 return 1; 5392 } 5393 5394 // This is called when scanning the full text search index. Scanning stops 5395 // when we no longer have a prefix match for the word 5396 static int ndb_text_search_next_word(MDB_cursor *cursor, MDB_cursor_op op, 5397 MDB_val *k, struct ndb_word *search_word, 5398 struct ndb_text_search_result *last_result, 5399 struct ndb_text_search_result *result, 5400 MDB_cursor_op order_op) 5401 { 5402 struct cursor key_cursor; 5403 //struct ndb_text_search_key search_key; 5404 MDB_val v; 5405 int retries; 5406 retries = -1; 5407 5408 make_cursor(k->mv_data, (unsigned char *)k->mv_data + k->mv_size, &key_cursor); 5409 5410 // When op is MDB_SET_RANGE, this initializes the search. Position 5411 // the cursor at the next key greater than or equal to the specified 5412 // key. 5413 // 5414 // Subsequent searches should use MDB_NEXT 5415 if (mdb_cursor_get(cursor, k, &v, op)) { 5416 // we should only do this if we're going in reverse 5417 if (op == MDB_SET_RANGE && order_op == MDB_PREV) { 5418 // if set range worked and our key exists, it should be 5419 // the one right before this one 5420 if (mdb_cursor_get(cursor, k, &v, MDB_PREV)) 5421 return 0; 5422 } else { 5423 return 0; 5424 } 5425 } 5426 5427 retry: 5428 retries++; 5429 /* 5430 printf("continuing from "); 5431 if (ndb_unpack_text_search_key(k->mv_data, k->mv_size, &search_key)) { 5432 ndb_print_text_search_key(&search_key); 5433 } else { printf("??"); } 5434 printf("\n"); 5435 */ 5436 5437 make_cursor(k->mv_data, (unsigned char *)k->mv_data + k->mv_size, &key_cursor); 5438 5439 if (unlikely(!ndb_unpack_text_search_key_noteid(&key_cursor, &result->key.note_id))) { 5440 fprintf(stderr, "UNUSUAL: failed to unpack text search key note_id\n"); 5441 return 0; 5442 } 5443 5444 // if the note id doesn't match the last result, then we stop trying 5445 // each search word 5446 if (last_result && last_result->key.note_id != result->key.note_id) { 5447 return 0; 5448 } 5449 5450 // On success, this could still be not related at all. 5451 // It could just be adjacent to the word. Let's check 5452 // if we have a matching prefix at least. 5453 5454 // Before we unpack the entire key, let's quickly 5455 // unpack just the string to check the prefix. We don't 5456 // need to unpack the entire key if the prefix doesn't 5457 // match 5458 if (!ndb_unpack_text_search_key_string(&key_cursor, 5459 &result->key.str, 5460 &result->key.str_len)) { 5461 // this should never happen 5462 fprintf(stderr, "UNUSUAL: failed to unpack text search key string\n"); 5463 return 0; 5464 } 5465 5466 if (!ndb_prefix_matches(result, search_word)) { 5467 /* 5468 printf("result prefix '%.*s' didn't match search word '%.*s'\n", 5469 result->key.str_len, result->key.str, 5470 search_word->word_len, search_word->word); 5471 */ 5472 // we should only do this if we're going in reverse 5473 if (retries == 0 && op == MDB_SET_RANGE && order_op == MDB_PREV) { 5474 // if set range worked and our key exists, it should be 5475 // the one right before this one 5476 mdb_cursor_get(cursor, k, &v, MDB_PREV); 5477 goto retry; 5478 } else { 5479 return 0; 5480 } 5481 } 5482 5483 // Unpack the remaining text search key, we will need this information 5484 // when building up our search results. 5485 if (!ndb_unpack_remaining_text_search_key(&key_cursor, &result->key)) { 5486 // This should never happen 5487 fprintf(stderr, "UNUSUAL: failed to unpack text search key\n"); 5488 return 0; 5489 } 5490 5491 /* 5492 if (last_result) { 5493 if (result->key.word_index < last_result->key.word_index) { 5494 fprintf(stderr, "skipping '%.*s' because it is before last result '%.*s'\n", 5495 result->key.str_len, result->key.str, 5496 last_result->key.str_len, last_result->key.str); 5497 return 0; 5498 } 5499 } 5500 */ 5501 5502 return 1; 5503 } 5504 5505 static void ndb_text_search_results_init( 5506 struct ndb_text_search_results *results) { 5507 results->num_results = 0; 5508 } 5509 5510 void ndb_default_text_search_config(struct ndb_text_search_config *cfg) 5511 { 5512 cfg->order = NDB_ORDER_DESCENDING; 5513 cfg->limit = MAX_TEXT_SEARCH_RESULTS; 5514 } 5515 5516 void ndb_text_search_config_set_order(struct ndb_text_search_config *cfg, 5517 enum ndb_search_order order) 5518 { 5519 cfg->order = order; 5520 } 5521 5522 void ndb_text_search_config_set_limit(struct ndb_text_search_config *cfg, int limit) 5523 { 5524 cfg->limit = limit; 5525 } 5526 5527 static int compare_search_words(const void *pa, const void *pb) 5528 { 5529 struct ndb_word *a, *b; 5530 5531 a = (struct ndb_word *)pa; 5532 b = (struct ndb_word *)pb; 5533 5534 if (a->word_len == b->word_len) { 5535 return 0; 5536 } else if (a->word_len > b->word_len) { 5537 // biggest words should be at the front of the list, 5538 // so we say it's "smaller" here 5539 return -1; 5540 } else { 5541 return 1; 5542 } 5543 } 5544 5545 // Sort search words from largest to smallest. Larger words are less likely 5546 // in the index, allowing our scan to walk fewer words at the root when 5547 // recursively matching. 5548 void sort_largest_to_smallest(struct ndb_search_words *words) 5549 { 5550 qsort(words->words, words->num_words, sizeof(words->words[0]), compare_search_words); 5551 } 5552 5553 5554 int ndb_text_search_with(struct ndb_txn *txn, const char *query, 5555 struct ndb_text_search_results *results, 5556 struct ndb_text_search_config *config, 5557 struct ndb_filter *filter) 5558 { 5559 unsigned char buffer[1024], *buf; 5560 unsigned char saved_buf[1024], *saved; 5561 struct ndb_text_search_result *result, *last_result; 5562 struct ndb_text_search_result candidate, last_candidate; 5563 struct ndb_search_words search_words; 5564 //struct ndb_text_search_key search_key; 5565 struct ndb_word *search_word; 5566 struct ndb_note *note; 5567 struct cursor cur; 5568 uint64_t since, until, timestamp_op, *pint; 5569 size_t note_size; 5570 ndb_text_search_key_order_fn key_order_fn; 5571 MDB_dbi text_db; 5572 MDB_cursor *cursor; 5573 MDB_val k, v; 5574 int i, j, keysize, saved_size, limit; 5575 MDB_cursor_op op, order_op; 5576 5577 note_size = 0; 5578 note = 0; 5579 saved = NULL; 5580 ndb_text_search_results_init(results); 5581 ndb_search_words_init(&search_words); 5582 5583 until = UINT64_MAX; 5584 since = 0; 5585 limit = MAX_TEXT_SEARCH_RESULTS; 5586 5587 // until, since from filter 5588 if (filter != NULL) { 5589 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_UNTIL))) 5590 until = *pint; 5591 5592 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_SINCE))) 5593 since = *pint; 5594 5595 if ((pint = ndb_filter_get_int(filter, NDB_FILTER_LIMIT))) 5596 limit = *pint; 5597 } 5598 5599 order_op = MDB_PREV; 5600 key_order_fn = ndb_make_text_search_key_high; 5601 timestamp_op = until; 5602 if (config) { 5603 if (config->order == NDB_ORDER_ASCENDING) { 5604 order_op = MDB_NEXT; 5605 // set the min timestamp value to since when ascending 5606 timestamp_op = since; 5607 key_order_fn = ndb_make_text_search_key_low; 5608 } 5609 limit = min(limit, config->limit); 5610 } 5611 // end search config 5612 5613 text_db = txn->lmdb->dbs[NDB_DB_NOTE_TEXT]; 5614 make_cursor((unsigned char *)query, (unsigned char *)query + strlen(query), &cur); 5615 5616 ndb_parse_words(&cur, &search_words, ndb_parse_search_words); 5617 if (search_words.num_words == 0) 5618 return 0; 5619 5620 if ((i = mdb_cursor_open(txn->mdb_txn, text_db, &cursor))) { 5621 fprintf(stderr, "nd_text_search: mdb_cursor_open failed, error %d\n", i); 5622 return 0; 5623 } 5624 5625 // This should complete the query quicker because the larger words are 5626 // likely to have fewer entries in the search index. This is not always 5627 // true. Words with higher frequency (like bitcoin on nostr in 2024) 5628 // may be slower. TODO: Skip word recursion by leveraging a minimal 5629 // perfect hashmap of parsed words on a note 5630 sort_largest_to_smallest(&search_words); 5631 5632 // for each word, we recursively find all of the submatches 5633 while (results->num_results < limit) { 5634 last_result = NULL; 5635 result = &results->results[results->num_results]; 5636 5637 // if we have saved, then we continue from the last root search 5638 // sequence 5639 if (saved) { 5640 buf = saved_buf; 5641 saved = NULL; 5642 keysize = saved_size; 5643 5644 k.mv_data = buf; 5645 k.mv_size = saved_size; 5646 5647 // reposition the cursor so we can continue 5648 if (mdb_cursor_get(cursor, &k, &v, MDB_SET_RANGE)) 5649 break; 5650 5651 op = order_op; 5652 } else { 5653 // construct a packed fulltext search key using this 5654 // word. This key doesn't contain any timestamp or index 5655 // info, so it should range match instead of exact 5656 // match 5657 if (!key_order_fn(buffer, sizeof(buffer), 5658 search_words.words[0].word_len, 5659 search_words.words[0].word, 5660 timestamp_op, 5661 &keysize)) 5662 { 5663 // word is too big to fit in 1024-sized key 5664 continue; 5665 } 5666 5667 buf = buffer; 5668 op = MDB_SET_RANGE; 5669 } 5670 5671 for (j = 0; j < search_words.num_words; j++) { 5672 search_word = &search_words.words[j]; 5673 5674 // shouldn't happen but let's be defensive a bit 5675 if (search_word->word_len == 0) 5676 continue; 5677 5678 // if we already matched a note in this phrase, make 5679 // sure we're including the note id in the query 5680 if (last_result) { 5681 // we are narrowing down a search. 5682 // if we already have this note id, just continue 5683 for (i = 0; i < results->num_results; i++) { 5684 if (results->results[i].key.note_id == last_result->key.note_id) 5685 // we can't break here to 5686 // leave the word loop so 5687 // have to use a goto 5688 goto cont; 5689 } 5690 5691 if (!ndb_make_noted_text_search_key( 5692 buffer, sizeof(buffer), 5693 search_word->word_len, 5694 search_word->word, 5695 last_result->key.timestamp, 5696 last_result->key.note_id, 5697 &keysize)) 5698 { 5699 continue; 5700 } 5701 5702 buf = buffer; 5703 } 5704 5705 k.mv_data = buf; 5706 k.mv_size = keysize; 5707 5708 // TODO: we can speed this up with the minimal perfect 5709 // hashmap by quickly rejecting the remaining words 5710 // by looking in the word hashmap on the note. This 5711 // would allow us to skip the recursive word lookup 5712 // thing 5713 if (!ndb_text_search_next_word(cursor, op, &k, 5714 search_word, 5715 last_result, 5716 &candidate, 5717 order_op)) { 5718 // we didn't find a match for this note_id 5719 if (j == 0) 5720 // if we're at one of the root words, 5721 // this means that there are no further 5722 // root word matches for any note, so 5723 // we know we're done 5724 goto done; 5725 else 5726 break; 5727 } 5728 5729 *result = candidate; 5730 op = MDB_SET_RANGE; 5731 5732 // save the first key match, since we will continue from 5733 // this on the next root word result 5734 if (j == 0) { 5735 if (!saved) { 5736 memcpy(saved_buf, k.mv_data, k.mv_size); 5737 saved = saved_buf; 5738 saved_size = k.mv_size; 5739 } 5740 5741 // since we will be trying to match the same 5742 // note_id on all subsequent word matches, 5743 // let's lookup this note and make sure it 5744 // matches the filter if we have one. If it 5745 // doesn't match, we can quickly skip the 5746 // remaining word queries 5747 if (filter) { 5748 if ((note = ndb_get_note_by_key(txn, 5749 result->key.note_id, 5750 ¬e_size))) 5751 { 5752 if (!ndb_filter_matches(filter, note)) { 5753 break; 5754 } 5755 result->note = note; 5756 result->note_size = note_size; 5757 } 5758 } 5759 } 5760 5761 result->note = note; 5762 result->note_size = note_size; 5763 last_candidate = *result; 5764 last_result = &last_candidate; 5765 } 5766 5767 // we matched all of the queries! 5768 if (j == search_words.num_words) { 5769 results->num_results++; 5770 } 5771 5772 cont: 5773 ; 5774 } 5775 5776 done: 5777 mdb_cursor_close(cursor); 5778 5779 return 1; 5780 } 5781 5782 int ndb_text_search(struct ndb_txn *txn, const char *query, 5783 struct ndb_text_search_results *results, 5784 struct ndb_text_search_config *config) 5785 { 5786 return ndb_text_search_with(txn, query, results, config, NULL); 5787 } 5788 5789 static void ndb_write_blocks(struct ndb_txn *txn, uint64_t note_key, 5790 struct ndb_blocks *blocks) 5791 { 5792 int rc; 5793 MDB_val key, val; 5794 5795 // make sure we're not writing the owned flag to the db 5796 blocks->flags &= ~NDB_BLOCK_FLAG_OWNED; 5797 5798 key.mv_data = ¬e_key; 5799 key.mv_size = sizeof(note_key); 5800 val.mv_data = blocks; 5801 val.mv_size = ndb_blocks_total_size(blocks); 5802 assert((val.mv_size % 8) == 0); 5803 5804 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_BLOCKS], &key, &val, 0))) { 5805 ndb_debug("write version to note_blocks failed: %s\n", 5806 mdb_strerror(rc)); 5807 return; 5808 } 5809 } 5810 5811 static int ndb_write_new_blocks(struct ndb_txn *txn, struct ndb_note *note, 5812 uint64_t note_key, unsigned char *scratch, 5813 size_t scratch_size) 5814 { 5815 size_t content_len; 5816 const char *content; 5817 struct ndb_blocks *blocks; 5818 5819 content_len = ndb_note_content_length(note); 5820 content = ndb_note_content(note); 5821 5822 if (!ndb_parse_content(scratch, scratch_size, content, content_len, &blocks)) { 5823 //ndb_debug("failed to parse content '%.*s'\n", content_len, content); 5824 return 0; 5825 } 5826 5827 ndb_write_blocks(txn, note_key, blocks); 5828 return 1; 5829 } 5830 5831 5832 // find the last id tag in a note (e, p, etc) 5833 static unsigned char *ndb_note_first_tag_id(struct ndb_note *note, char tag) 5834 { 5835 struct ndb_iterator iter; 5836 struct ndb_str str; 5837 5838 // get the liked event id (last id) 5839 ndb_tags_iterate_start(note, &iter); 5840 5841 while (ndb_tags_iterate_next(&iter)) { 5842 if (iter.tag->count < 2) 5843 continue; 5844 5845 str = ndb_tag_str(note, iter.tag, 0); 5846 5847 // assign liked to the last e tag 5848 if (str.flag == NDB_PACKED_STR && str.str[0] == tag) { 5849 str = ndb_tag_str(note, iter.tag, 1); 5850 if (str.flag == NDB_PACKED_ID) 5851 return str.id; 5852 } 5853 } 5854 5855 return NULL; 5856 } 5857 5858 static int ndb_increment_quote_metadata( 5859 struct ndb_txn *txn, 5860 unsigned char *quoted_note_id, 5861 unsigned char *scratch, 5862 size_t scratch_size) 5863 { 5864 MDB_val key, val; 5865 uint16_t *quotes; 5866 struct ndb_note_meta *meta; 5867 struct ndb_note_meta_entry *entry; 5868 int rc; 5869 5870 meta = ndb_get_note_meta(txn, quoted_note_id); 5871 rc = ndb_note_meta_clone_with_entry(&meta, &entry, 5872 NDB_NOTE_META_COUNTS, 5873 NULL, /* payload to match. only relevant for reactions */ 5874 scratch, 5875 scratch_size); 5876 5877 switch (rc) { 5878 case NDB_META_CLONE_FAILED: 5879 return 0; 5880 case NDB_META_CLONE_NEW_ENTRY: 5881 ndb_note_meta_counts_set(entry, 0, 1, 0, 0, 0); 5882 break; 5883 case NDB_META_CLONE_EXISTING_ENTRY: 5884 quotes = ndb_note_meta_counts_quotes(entry); 5885 (*quotes)++; 5886 break; 5887 } 5888 5889 key.mv_data = quoted_note_id; 5890 key.mv_size = 32; 5891 5892 val.mv_data = meta; 5893 val.mv_size = ndb_note_meta_total_size(meta); 5894 assert((val.mv_size % 8) == 0); 5895 5896 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { 5897 ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); 5898 return 0; 5899 } 5900 5901 return 1; 5902 } 5903 5904 /* update reply count metadata for a specific note id */ 5905 static int ndb_increment_direct_reply_metadata( 5906 struct ndb_txn *txn, 5907 unsigned char *id, 5908 unsigned char *scratch, 5909 size_t scratch_size) 5910 { 5911 MDB_val key, val; 5912 uint16_t *direct_replies; 5913 struct ndb_note_meta *meta; 5914 struct ndb_note_meta_entry *entry; 5915 int rc; 5916 5917 meta = ndb_get_note_meta(txn, id); 5918 rc = ndb_note_meta_clone_with_entry(&meta, &entry, 5919 NDB_NOTE_META_COUNTS, 5920 NULL, /* payload to match. only relevant for reactions */ 5921 scratch, 5922 scratch_size); 5923 5924 switch (rc) { 5925 case NDB_META_CLONE_FAILED: 5926 return 0; 5927 case NDB_META_CLONE_NEW_ENTRY: 5928 ndb_note_meta_counts_set(entry, 0, 0, 1, 0, 0); 5929 break; 5930 case NDB_META_CLONE_EXISTING_ENTRY: 5931 direct_replies = ndb_note_meta_counts_direct_replies(entry); 5932 (*direct_replies)++; 5933 break; 5934 } 5935 5936 key.mv_data = id; 5937 key.mv_size = 32; 5938 5939 val.mv_data = meta; 5940 val.mv_size = ndb_note_meta_total_size(meta); 5941 assert((val.mv_size % 8) == 0); 5942 5943 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { 5944 ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); 5945 return 0; 5946 } 5947 5948 return 1; 5949 } 5950 5951 /* update reply count metadata for a specific note id */ 5952 static int ndb_increment_thread_reply_metadata( 5953 struct ndb_txn *txn, 5954 unsigned char *id, 5955 unsigned char *scratch, 5956 size_t scratch_size) 5957 { 5958 MDB_val key, val; 5959 uint32_t *replies; 5960 struct ndb_note_meta *meta; 5961 struct ndb_note_meta_entry *entry; 5962 int rc; 5963 5964 meta = ndb_get_note_meta(txn, id); 5965 rc = ndb_note_meta_clone_with_entry(&meta, &entry, 5966 NDB_NOTE_META_COUNTS, 5967 NULL, /* payload to match. only relevant for reactions */ 5968 scratch, 5969 scratch_size); 5970 5971 switch (rc) { 5972 case NDB_META_CLONE_FAILED: 5973 return 0; 5974 case NDB_META_CLONE_NEW_ENTRY: 5975 ndb_note_meta_counts_set(entry, 0, 0, 0, 1, 0); 5976 break; 5977 case NDB_META_CLONE_EXISTING_ENTRY: 5978 replies = ndb_note_meta_counts_thread_replies(entry); 5979 (*replies)++; 5980 break; 5981 } 5982 5983 key.mv_data = id; 5984 key.mv_size = 32; 5985 5986 val.mv_data = meta; 5987 val.mv_size = ndb_note_meta_total_size(meta); 5988 assert((val.mv_size % 8) == 0); 5989 5990 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { 5991 ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); 5992 return 0; 5993 } 5994 5995 return 1; 5996 } 5997 5998 /* update reply count metadata for a specific note id */ 5999 static int ndb_increment_repost_metadata( 6000 struct ndb_txn *txn, 6001 unsigned char *id, 6002 unsigned char *scratch, 6003 size_t scratch_size) 6004 { 6005 MDB_val key, val; 6006 uint16_t *reposts; 6007 struct ndb_note_meta *meta; 6008 struct ndb_note_meta_entry *entry; 6009 int rc; 6010 6011 meta = ndb_get_note_meta(txn, id); 6012 rc = ndb_note_meta_clone_with_entry(&meta, &entry, 6013 NDB_NOTE_META_COUNTS, 6014 NULL, /* payload to match. only relevant for reactions */ 6015 scratch, 6016 scratch_size); 6017 6018 switch (rc) { 6019 case NDB_META_CLONE_FAILED: 6020 return 0; 6021 case NDB_META_CLONE_NEW_ENTRY: 6022 ndb_note_meta_counts_set(entry, 0, 0, 0, 0, 1); 6023 break; 6024 case NDB_META_CLONE_EXISTING_ENTRY: 6025 reposts = ndb_note_meta_counts_reposts(entry); 6026 (*reposts)++; 6027 break; 6028 } 6029 6030 key.mv_data = id; 6031 key.mv_size = 32; 6032 6033 val.mv_data = meta; 6034 val.mv_size = ndb_note_meta_total_size(meta); 6035 assert((val.mv_size % 8) == 0); 6036 6037 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { 6038 ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); 6039 return 0; 6040 } 6041 6042 return 1; 6043 } 6044 6045 static void ndb_process_repost_stats(struct ndb_txn *txn, struct ndb_note *note, unsigned char *scratch, size_t scratch_size) 6046 { 6047 unsigned char *reposted_note_id; 6048 reposted_note_id = ndb_note_first_tag_id(note, 'e'); 6049 6050 /* find q tag to see if we are quoting anything */ 6051 if (reposted_note_id) { 6052 ndb_increment_repost_metadata(txn, reposted_note_id, scratch, scratch_size); 6053 } 6054 } 6055 6056 /* process quote and reply count metadata */ 6057 static void ndb_process_note_stats( 6058 struct ndb_txn *txn, 6059 struct ndb_note *note, 6060 unsigned char *scratch, 6061 size_t scratch_size) 6062 { 6063 unsigned char *quoted_note_id, *reply_id; 6064 struct ndb_note_reply reply; 6065 6066 reply_id = NULL; 6067 6068 /* find q tag to see if we are quoting anything */ 6069 if ((quoted_note_id = ndb_note_first_tag_id(note, 'q'))) { 6070 ndb_increment_quote_metadata(txn, quoted_note_id, scratch, scratch_size); 6071 } 6072 6073 ndb_parse_reply(note, &reply); 6074 if (ndb_is_reply_to_root(&reply)) { 6075 reply_id = reply.root; 6076 } else { 6077 reply_id = reply.reply; 6078 } 6079 6080 if (reply_id) { 6081 ndb_increment_direct_reply_metadata(txn, reply_id, scratch, scratch_size); 6082 } 6083 6084 if (reply.root) { 6085 ndb_increment_thread_reply_metadata(txn, reply.root, scratch, scratch_size); 6086 } 6087 } 6088 6089 static uint64_t ndb_write_note(struct ndb_txn *txn, 6090 struct ndb_writer_note *note, 6091 unsigned char *scratch, size_t scratch_size, 6092 uint32_t ndb_flags) 6093 { 6094 int rc; 6095 uint64_t note_key, kind; 6096 struct ndb_relay_kind_key relay_key; 6097 MDB_dbi note_db; 6098 MDB_val key, val; 6099 6100 kind = note->note->kind; 6101 6102 // let's quickly sanity check if we already have this note 6103 if ((note_key = ndb_get_notekey_by_id(txn, note->note->id))) { 6104 if (ndb_relay_kind_key_init(&relay_key, note_key, kind, ndb_note_created_at(note->note), note->relay)) 6105 ndb_write_note_relay_indexes(txn, &relay_key); 6106 return 0; 6107 } 6108 6109 // get dbs 6110 note_db = txn->lmdb->dbs[NDB_DB_NOTE]; 6111 6112 // get new key 6113 note_key = ndb_get_last_key(txn->mdb_txn, note_db) + 1; 6114 6115 // write note to event store 6116 key.mv_data = ¬e_key; 6117 key.mv_size = sizeof(note_key); 6118 val.mv_data = note->note; 6119 val.mv_size = note->note_len; 6120 6121 if ((rc = mdb_put(txn->mdb_txn, note_db, &key, &val, 0))) { 6122 ndb_debug("write note to db failed: %s\n", mdb_strerror(rc)); 6123 return 0; 6124 } 6125 6126 ndb_write_note_id_index(txn, note->note, note_key); 6127 ndb_write_note_kind_index(txn, note->note, note_key); 6128 ndb_write_note_tag_index(txn, note->note, note_key); 6129 ndb_write_note_pubkey_index(txn, note->note, note_key); 6130 ndb_write_note_pubkey_kind_index(txn, note->note, note_key); 6131 6132 if (ndb_relay_kind_key_init(&relay_key, note_key, kind, ndb_note_created_at(note->note), note->relay)) 6133 ndb_write_note_relay_indexes(txn, &relay_key); 6134 6135 // only parse content and do fulltext index on text and longform notes 6136 if (kind == 1 || kind == 30023) { 6137 if (!ndb_flag_set(ndb_flags, NDB_FLAG_NO_FULLTEXT)) { 6138 if (!ndb_write_note_fulltext_index(txn, note->note, note_key)) 6139 return 0; 6140 } 6141 6142 // write note blocks 6143 if (!ndb_flag_set(ndb_flags, NDB_FLAG_NO_NOTE_BLOCKS)) { 6144 ndb_write_new_blocks(txn, note->note, note_key, scratch, scratch_size); 6145 } 6146 6147 ndb_process_note_stats(txn, note->note, scratch, scratch_size); 6148 } else if (kind == 7 && !ndb_flag_set(ndb_flags, NDB_FLAG_NO_STATS)) { 6149 ndb_write_reaction_stats(txn, note->note, scratch, scratch_size); 6150 } else if (kind == 6 || kind == 16) { 6151 ndb_process_repost_stats(txn, note->note, scratch, scratch_size); 6152 } 6153 6154 return note_key; 6155 } 6156 6157 static void ndb_monitor_lock(struct ndb_monitor *mon) { 6158 pthread_mutex_lock(&mon->mutex); 6159 } 6160 6161 static void ndb_monitor_unlock(struct ndb_monitor *mon) { 6162 pthread_mutex_unlock(&mon->mutex); 6163 } 6164 6165 struct written_note { 6166 uint64_t note_id; 6167 struct ndb_writer_note *note; 6168 }; 6169 6170 // When the data has been committed to the database, take all of the written 6171 // notes, check them against subscriptions, and then write to the subscription 6172 // inbox for all matching notes 6173 static void ndb_notify_subscriptions(struct ndb_monitor *monitor, 6174 struct written_note *wrote, int num_notes) 6175 { 6176 int i, k; 6177 int pushed; 6178 struct written_note *written; 6179 struct ndb_note *note; 6180 struct ndb_subscription *sub; 6181 6182 ndb_monitor_lock(monitor); 6183 6184 for (i = 0; i < monitor->num_subscriptions; i++) { 6185 sub = &monitor->subscriptions[i]; 6186 ndb_debug("checking subscription %d, %d notes\n", i, num_notes); 6187 6188 pushed = 0; 6189 for (k = 0; k < num_notes; k++) { 6190 written = &wrote[k]; 6191 note = written->note->note; 6192 6193 if (ndb_filter_group_matches(&sub->group, note)) { 6194 ndb_debug("pushing note\n"); 6195 6196 if (!prot_queue_push(&sub->inbox, &written->note_id)) { 6197 ndb_debug("couldn't push note to subscriber"); 6198 } else { 6199 pushed++; 6200 } 6201 } else { 6202 ndb_debug("not pushing note\n"); 6203 } 6204 } 6205 6206 // After pushing all of the matching notes, check to see if we 6207 // have a registered subscription callback. If so, we call it. 6208 // The callback needs to call ndb_poll_for_notes to pull data 6209 // that was just pushed to the queue in the for loop above. 6210 if (monitor->sub_cb != NULL && pushed > 0) { 6211 monitor->sub_cb(monitor->sub_cb_ctx, sub->subid); 6212 } 6213 } 6214 6215 ndb_monitor_unlock(monitor); 6216 } 6217 6218 uint64_t ndb_write_note_and_profile( 6219 struct ndb_txn *txn, 6220 struct ndb_writer_profile *profile, 6221 unsigned char *scratch, 6222 size_t scratch_size, 6223 uint32_t ndb_flags) 6224 { 6225 uint64_t note_nkey; 6226 6227 note_nkey = ndb_write_note(txn, &profile->note, scratch, scratch_size, ndb_flags); 6228 6229 if (profile->record.builder) { 6230 // only write if parsing didn't fail 6231 ndb_write_profile(txn, profile, note_nkey); 6232 } 6233 6234 return note_nkey; 6235 } 6236 6237 // only to be called from the writer thread 6238 static int ndb_write_version(struct ndb_txn *txn, uint64_t version) 6239 { 6240 int rc; 6241 MDB_val key, val; 6242 uint64_t version_key; 6243 6244 version_key = NDB_META_KEY_VERSION; 6245 6246 key.mv_data = &version_key; 6247 key.mv_size = sizeof(version_key); 6248 val.mv_data = &version; 6249 val.mv_size = sizeof(version); 6250 6251 if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NDB_META], &key, &val, 0))) { 6252 ndb_debug("write version to ndb_meta failed: %s\n", 6253 mdb_strerror(rc)); 6254 return 0; 6255 } 6256 6257 //fprintf(stderr, "writing version %" PRIu64 "\n", version); 6258 return 1; 6259 } 6260 6261 6262 static int ndb_run_migrations(struct ndb_txn *txn) 6263 { 6264 int64_t version, latest_version, i; 6265 6266 latest_version = sizeof(MIGRATIONS) / sizeof(MIGRATIONS[0]); 6267 6268 if ((version = ndb_db_version(txn)) == -1) { 6269 ndb_debug("run_migrations: no version found, assuming new db\n"); 6270 version = latest_version; 6271 6272 // no version found. fresh db? 6273 if (!ndb_write_version(txn, version)) { 6274 fprintf(stderr, "run_migrations: failed writing db version"); 6275 return 0; 6276 } 6277 6278 return 1; 6279 } else { 6280 ndb_debug("ndb: version %" PRIu64 " found\n", version); 6281 } 6282 6283 if (version < latest_version) 6284 fprintf(stderr, "nostrdb: migrating v%d -> v%d\n", 6285 (int)version, (int)latest_version); 6286 6287 for (i = version; i < latest_version; i++) { 6288 if (!MIGRATIONS[i].fn(txn)) { 6289 fprintf(stderr, "run_migrations: migration v%d -> v%d failed\n", (int)i, (int)(i+1)); 6290 return 0; 6291 } 6292 6293 if (!ndb_write_version(txn, i+1)) { 6294 fprintf(stderr, "run_migrations: failed writing db version"); 6295 return 0; 6296 } 6297 6298 version = i+1; 6299 } 6300 6301 return 1; 6302 } 6303 6304 6305 static void *ndb_writer_thread(void *data) 6306 { 6307 ndb_debug("started writer thread\n"); 6308 struct ndb_writer *writer = data; 6309 struct ndb_writer_msg msgs[THREAD_QUEUE_BATCH], *msg; 6310 struct written_note written_notes[THREAD_QUEUE_BATCH]; 6311 int i, popped, done, needs_commit, num_notes; 6312 uint64_t note_nkey; 6313 struct ndb_txn txn; 6314 unsigned char *scratch; 6315 struct ndb_relay_kind_key relay_key; 6316 6317 // 2MB scratch buffer for parsing note content 6318 scratch = malloc(writer->scratch_size); 6319 MDB_txn *mdb_txn = NULL; 6320 ndb_txn_from_mdb(&txn, writer->lmdb, mdb_txn); 6321 6322 done = 0; 6323 while (!done) { 6324 txn.mdb_txn = NULL; 6325 num_notes = 0; 6326 ndb_debug("writer waiting for items\n"); 6327 popped = prot_queue_pop_all(&writer->inbox, msgs, THREAD_QUEUE_BATCH); 6328 ndb_debug("writer popped %d items\n", popped); 6329 6330 needs_commit = 0; 6331 for (i = 0 ; i < popped; i++) { 6332 msg = &msgs[i]; 6333 switch (msg->type) { 6334 case NDB_WRITER_NOTE: 6335 case NDB_WRITER_NOTE_META: 6336 case NDB_WRITER_PROFILE: 6337 case NDB_WRITER_DBMETA: 6338 case NDB_WRITER_PROFILE_LAST_FETCH: 6339 case NDB_WRITER_BLOCKS: 6340 case NDB_WRITER_MIGRATE: 6341 case NDB_WRITER_NOTE_RELAY: 6342 needs_commit = 1; 6343 break; 6344 case NDB_WRITER_QUIT: break; 6345 } 6346 } 6347 6348 if (needs_commit && mdb_txn_begin(txn.lmdb->env, NULL, 0, (MDB_txn **)&txn.mdb_txn)) 6349 { 6350 fprintf(stderr, "writer thread txn_begin failed"); 6351 // should definitely not happen unless DB is full 6352 // or something ? 6353 continue; 6354 } 6355 6356 for (i = 0; i < popped; i++) { 6357 msg = &msgs[i]; 6358 6359 switch (msg->type) { 6360 case NDB_WRITER_QUIT: 6361 // quits are handled before this 6362 ndb_debug("writer thread got quit message\n"); 6363 done = 1; 6364 continue; 6365 case NDB_WRITER_PROFILE: 6366 note_nkey = 6367 ndb_write_note_and_profile( 6368 &txn, 6369 &msg->profile, 6370 scratch, 6371 writer->scratch_size, 6372 writer->ndb_flags); 6373 6374 if (note_nkey > 0) { 6375 written_notes[num_notes++] = 6376 (struct written_note){ 6377 .note_id = note_nkey, 6378 .note = &msg->profile.note, 6379 }; 6380 } else { 6381 ndb_debug("failed to write note\n"); 6382 } 6383 break; 6384 case NDB_WRITER_NOTE_META: 6385 ndb_writer_set_note_meta(&txn, msg->note_meta.note_id, msg->note_meta.metadata); 6386 break; 6387 6388 case NDB_WRITER_NOTE: 6389 note_nkey = ndb_write_note(&txn, &msg->note, 6390 scratch, 6391 writer->scratch_size, 6392 writer->ndb_flags); 6393 6394 if (note_nkey > 0) { 6395 written_notes[num_notes++] = (struct written_note){ 6396 .note_id = note_nkey, 6397 .note = &msg->note, 6398 }; 6399 } 6400 break; 6401 case NDB_WRITER_NOTE_RELAY: 6402 if (ndb_relay_kind_key_init(&relay_key, 6403 msg->note_relay.note_key, 6404 msg->note_relay.kind, 6405 msg->note_relay.created_at, 6406 msg->note_relay.relay)) 6407 { 6408 ndb_write_note_relay_indexes(&txn, &relay_key); 6409 } 6410 break; 6411 case NDB_WRITER_DBMETA: 6412 ndb_write_version(&txn, msg->ndb_meta.version); 6413 break; 6414 case NDB_WRITER_BLOCKS: 6415 ndb_write_blocks(&txn, msg->blocks.note_key, 6416 msg->blocks.blocks); 6417 break; 6418 case NDB_WRITER_MIGRATE: 6419 if (!ndb_run_migrations(&txn)) { 6420 mdb_txn_abort(txn.mdb_txn); 6421 goto bail; 6422 } 6423 break; 6424 case NDB_WRITER_PROFILE_LAST_FETCH: 6425 ndb_writer_last_profile_fetch(&txn, 6426 msg->last_fetch.pubkey, 6427 msg->last_fetch.fetched_at 6428 ); 6429 break; 6430 } 6431 } 6432 6433 // commit writes 6434 if (needs_commit) { 6435 if (!ndb_end_query(&txn)) { 6436 ndb_debug("writer thread txn commit failed\n"); 6437 } else { 6438 ndb_debug("notifying subscriptions, %d notes\n", num_notes); 6439 ndb_notify_subscriptions(writer->monitor, 6440 written_notes, 6441 num_notes); 6442 // update subscriptions 6443 } 6444 } 6445 6446 // free notes 6447 for (i = 0; i < popped; i++) { 6448 msg = &msgs[i]; 6449 if (msg->type == NDB_WRITER_NOTE) { 6450 free(msg->note.note); 6451 if (msg->note.relay) 6452 free((void*)msg->note.relay); 6453 } else if (msg->type == NDB_WRITER_PROFILE) { 6454 free(msg->profile.note.note); 6455 ndb_profile_record_builder_free(&msg->profile.record); 6456 } else if (msg->type == NDB_WRITER_BLOCKS) { 6457 ndb_blocks_free(msg->blocks.blocks); 6458 } else if (msg->type == NDB_WRITER_NOTE_RELAY) { 6459 free((void*)msg->note_relay.relay); 6460 } 6461 } 6462 } 6463 6464 bail: 6465 free(scratch); 6466 ndb_debug("quitting writer thread\n"); 6467 return NULL; 6468 } 6469 6470 static void *ndb_ingester_thread(void *data) 6471 { 6472 secp256k1_context *ctx; 6473 struct thread *thread = data; 6474 struct ndb_ingester *ingester = (struct ndb_ingester *)thread->ctx; 6475 struct ndb_lmdb *lmdb = ingester->lmdb; 6476 struct ndb_ingester_msg msgs[THREAD_QUEUE_BATCH], *msg; 6477 struct ndb_writer_msg outs[THREAD_QUEUE_BATCH], *out; 6478 int i, to_write, popped, done, any_event; 6479 MDB_txn *read_txn = NULL; 6480 unsigned char *scratch; 6481 int rc; 6482 6483 // this is used in note verification and anything else that 6484 // needs a temporary buffer 6485 scratch = malloc(ingester->scratch_size); 6486 6487 ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); 6488 //ndb_debug("started ingester thread\n"); 6489 6490 done = 0; 6491 while (!done) { 6492 to_write = 0; 6493 any_event = 0; 6494 6495 popped = prot_queue_pop_all(&thread->inbox, msgs, THREAD_QUEUE_BATCH); 6496 ndb_debug("ingester popped %d items\n", popped); 6497 6498 for (i = 0; i < popped; i++) { 6499 msg = &msgs[i]; 6500 if (msg->type == NDB_INGEST_EVENT) { 6501 any_event = 1; 6502 break; 6503 } 6504 } 6505 6506 if (any_event && (rc = mdb_txn_begin(lmdb->env, NULL, MDB_RDONLY, &read_txn))) { 6507 // this is bad 6508 fprintf(stderr, "UNUSUAL ndb_ingester: mdb_txn_begin failed: '%s'\n", 6509 mdb_strerror(rc)); 6510 continue; 6511 } 6512 6513 for (i = 0; i < popped; i++) { 6514 msg = &msgs[i]; 6515 switch (msg->type) { 6516 case NDB_INGEST_QUIT: 6517 done = 1; 6518 break; 6519 6520 case NDB_INGEST_EVENT: 6521 out = &outs[to_write]; 6522 if (ndb_ingester_process_event(ctx, ingester, 6523 &msg->event, out, 6524 scratch, 6525 read_txn)) { 6526 to_write++; 6527 } 6528 } 6529 } 6530 6531 if (any_event) 6532 mdb_txn_abort(read_txn); 6533 6534 if (to_write > 0) { 6535 ndb_debug("pushing %d events to write queue\n", to_write); 6536 if (!prot_queue_push_all(ingester->writer_inbox, outs, to_write)) { 6537 ndb_debug("failed pushing %d events to write queue\n", to_write); 6538 } 6539 } 6540 } 6541 6542 ndb_debug("quitting ingester thread\n"); 6543 secp256k1_context_destroy(ctx); 6544 free(scratch); 6545 return NULL; 6546 } 6547 6548 6549 static int ndb_writer_init(struct ndb_writer *writer, struct ndb_lmdb *lmdb, 6550 struct ndb_monitor *monitor, uint32_t ndb_flags, 6551 int scratch_size) 6552 { 6553 writer->lmdb = lmdb; 6554 writer->monitor = monitor; 6555 writer->ndb_flags = ndb_flags; 6556 writer->scratch_size = scratch_size; 6557 writer->queue_buflen = sizeof(struct ndb_writer_msg) * DEFAULT_QUEUE_SIZE; 6558 writer->queue_buf = malloc(writer->queue_buflen); 6559 if (writer->queue_buf == NULL) { 6560 fprintf(stderr, "ndb: failed to allocate space for writer queue"); 6561 return 0; 6562 } 6563 6564 // init the writer queue. 6565 prot_queue_init(&writer->inbox, writer->queue_buf, 6566 writer->queue_buflen, sizeof(struct ndb_writer_msg)); 6567 6568 // spin up the writer thread 6569 if (THREAD_CREATE(writer->thread_id, ndb_writer_thread, writer)) 6570 { 6571 fprintf(stderr, "ndb writer thread failed to create\n"); 6572 return 0; 6573 } 6574 6575 return 1; 6576 } 6577 6578 // initialize the ingester queue and then spawn the thread 6579 static int ndb_ingester_init(struct ndb_ingester *ingester, 6580 struct ndb_lmdb *lmdb, 6581 struct prot_queue *writer_inbox, 6582 int scratch_size, 6583 const struct ndb_config *config) 6584 { 6585 int elem_size, num_elems; 6586 static struct ndb_ingester_msg quit_msg = { .type = NDB_INGEST_QUIT }; 6587 6588 // TODO: configurable queue sizes 6589 elem_size = sizeof(struct ndb_ingester_msg); 6590 num_elems = DEFAULT_QUEUE_SIZE; 6591 6592 ingester->scratch_size = scratch_size; 6593 ingester->writer_inbox = writer_inbox; 6594 ingester->lmdb = lmdb; 6595 ingester->flags = config->flags; 6596 ingester->filter = config->ingest_filter; 6597 ingester->filter_context = config->filter_context; 6598 6599 if (!threadpool_init(&ingester->tp, config->ingester_threads, 6600 elem_size, num_elems, &quit_msg, ingester, 6601 ndb_ingester_thread)) 6602 { 6603 fprintf(stderr, "ndb ingester threadpool failed to init\n"); 6604 return 0; 6605 } 6606 6607 return 1; 6608 } 6609 6610 static int ndb_writer_destroy(struct ndb_writer *writer) 6611 { 6612 struct ndb_writer_msg msg; 6613 6614 // kill thread 6615 msg.type = NDB_WRITER_QUIT; 6616 ndb_debug("writer: pushing quit message\n"); 6617 if (!prot_queue_push(&writer->inbox, &msg)) { 6618 // queue is too full to push quit message. just kill it. 6619 ndb_debug("writer: terminating thread\n"); 6620 THREAD_TERMINATE(writer->thread_id); 6621 } else { 6622 ndb_debug("writer: joining thread\n"); 6623 THREAD_FINISH(writer->thread_id); 6624 } 6625 6626 // cleanup 6627 ndb_debug("writer: cleaning up protected queue\n"); 6628 prot_queue_destroy(&writer->inbox); 6629 6630 free(writer->queue_buf); 6631 6632 return 1; 6633 } 6634 6635 static int ndb_ingester_destroy(struct ndb_ingester *ingester) 6636 { 6637 threadpool_destroy(&ingester->tp); 6638 return 1; 6639 } 6640 6641 static int ndb_init_lmdb(const char *filename, struct ndb_lmdb *lmdb, size_t mapsize) 6642 { 6643 int rc; 6644 MDB_txn *txn; 6645 6646 if ((rc = mdb_env_create(&lmdb->env))) { 6647 fprintf(stderr, "mdb_env_create failed, error %d\n", rc); 6648 return 0; 6649 } 6650 6651 if ((rc = mdb_env_set_mapsize(lmdb->env, mapsize))) { 6652 fprintf(stderr, "mdb_env_set_mapsize failed, error %d\n", rc); 6653 return 0; 6654 } 6655 6656 if ((rc = mdb_env_set_maxdbs(lmdb->env, NDB_DBS))) { 6657 fprintf(stderr, "mdb_env_set_maxdbs failed, error %d\n", rc); 6658 return 0; 6659 } 6660 6661 if ((rc = mdb_env_open(lmdb->env, filename, 0, 0664))) { 6662 fprintf(stderr, "mdb_env_open failed, error %d\n", rc); 6663 return 0; 6664 } 6665 6666 // Initialize DBs 6667 if ((rc = mdb_txn_begin(lmdb->env, NULL, 0, &txn))) { 6668 fprintf(stderr, "mdb_txn_begin failed, error %d\n", rc); 6669 return 0; 6670 } 6671 6672 // note flatbuffer db 6673 if ((rc = mdb_dbi_open(txn, "note", MDB_CREATE | MDB_INTEGERKEY, &lmdb->dbs[NDB_DB_NOTE]))) { 6674 fprintf(stderr, "mdb_dbi_open event failed, error %d\n", rc); 6675 return 0; 6676 } 6677 6678 // note metadata db 6679 if ((rc = mdb_dbi_open(txn, "meta", MDB_CREATE, &lmdb->dbs[NDB_DB_META]))) { 6680 fprintf(stderr, "mdb_dbi_open meta failed, error %d\n", rc); 6681 return 0; 6682 } 6683 6684 // profile flatbuffer db 6685 if ((rc = mdb_dbi_open(txn, "profile", MDB_CREATE | MDB_INTEGERKEY, &lmdb->dbs[NDB_DB_PROFILE]))) { 6686 fprintf(stderr, "mdb_dbi_open profile failed, error %d\n", rc); 6687 return 0; 6688 } 6689 6690 // profile search db 6691 if ((rc = mdb_dbi_open(txn, "profile_search", MDB_CREATE, &lmdb->dbs[NDB_DB_PROFILE_SEARCH]))) { 6692 fprintf(stderr, "mdb_dbi_open profile_search failed, error %d\n", rc); 6693 return 0; 6694 } 6695 mdb_set_compare(txn, lmdb->dbs[NDB_DB_PROFILE_SEARCH], ndb_search_key_cmp); 6696 6697 // ndb metadata (db version, etc) 6698 if ((rc = mdb_dbi_open(txn, "ndb_meta", MDB_CREATE | MDB_INTEGERKEY, &lmdb->dbs[NDB_DB_NDB_META]))) { 6699 fprintf(stderr, "mdb_dbi_open ndb_meta failed, error %d\n", rc); 6700 return 0; 6701 } 6702 6703 // profile last fetches 6704 if ((rc = mdb_dbi_open(txn, "profile_last_fetch", MDB_CREATE, &lmdb->dbs[NDB_DB_PROFILE_LAST_FETCH]))) { 6705 fprintf(stderr, "mdb_dbi_open profile last fetch, error %d\n", rc); 6706 return 0; 6707 } 6708 6709 // relay kind index. maps <relay_url><kind><created><note_id> primary keys to relay records 6710 // see ndb_relay_kind_cmp function for more details on the key format 6711 if ((rc = mdb_dbi_open(txn, "relay_kind", MDB_CREATE, &lmdb->dbs[NDB_DB_NOTE_RELAY_KIND]))) { 6712 fprintf(stderr, "mdb_dbi_open profile last fetch, error %d\n", rc); 6713 return 0; 6714 } 6715 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_RELAY_KIND], ndb_relay_kind_cmp); 6716 6717 // note_id -> relay index 6718 if ((rc = mdb_dbi_open(txn, "note_relays", MDB_CREATE | MDB_DUPSORT, &lmdb->dbs[NDB_DB_NOTE_RELAYS]))) { 6719 fprintf(stderr, "mdb_dbi_open profile last fetch, error %d\n", rc); 6720 return 0; 6721 } 6722 6723 // id+ts index flags 6724 unsigned int tsid_flags = MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED; 6725 6726 // index dbs 6727 if ((rc = mdb_dbi_open(txn, "note_id", tsid_flags, &lmdb->dbs[NDB_DB_NOTE_ID]))) { 6728 fprintf(stderr, "mdb_dbi_open id failed: %s\n", mdb_strerror(rc)); 6729 return 0; 6730 } 6731 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_ID], ndb_tsid_compare); 6732 6733 if ((rc = mdb_dbi_open(txn, "profile_pk", tsid_flags, &lmdb->dbs[NDB_DB_PROFILE_PK]))) { 6734 fprintf(stderr, "mdb_dbi_open profile_pk failed: %s\n", mdb_strerror(rc)); 6735 return 0; 6736 } 6737 mdb_set_compare(txn, lmdb->dbs[NDB_DB_PROFILE_PK], ndb_tsid_compare); 6738 6739 if ((rc = mdb_dbi_open(txn, "note_kind", 6740 MDB_CREATE | MDB_DUPSORT | MDB_INTEGERDUP | MDB_DUPFIXED, 6741 &lmdb->dbs[NDB_DB_NOTE_KIND]))) { 6742 fprintf(stderr, "mdb_dbi_open note_kind failed: %s\n", mdb_strerror(rc)); 6743 return 0; 6744 } 6745 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_KIND], ndb_u64_ts_compare); 6746 6747 if ((rc = mdb_dbi_open(txn, "note_pubkey", 6748 MDB_CREATE | MDB_DUPSORT | MDB_INTEGERDUP | MDB_DUPFIXED, 6749 &lmdb->dbs[NDB_DB_NOTE_PUBKEY]))) { 6750 fprintf(stderr, "mdb_dbi_open note_pubkey failed: %s\n", mdb_strerror(rc)); 6751 return 0; 6752 } 6753 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_PUBKEY], ndb_tsid_compare); 6754 6755 if ((rc = mdb_dbi_open(txn, "note_pubkey_kind", 6756 MDB_CREATE | MDB_DUPSORT | MDB_INTEGERDUP | MDB_DUPFIXED, 6757 &lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND]))) { 6758 fprintf(stderr, "mdb_dbi_open note_pubkey_kind failed: %s\n", mdb_strerror(rc)); 6759 return 0; 6760 } 6761 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND], ndb_id_u64_ts_compare); 6762 6763 if ((rc = mdb_dbi_open(txn, "note_text", MDB_CREATE | MDB_DUPSORT, 6764 &lmdb->dbs[NDB_DB_NOTE_TEXT]))) { 6765 fprintf(stderr, "mdb_dbi_open note_text failed: %s\n", mdb_strerror(rc)); 6766 return 0; 6767 } 6768 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_TEXT], ndb_text_search_key_compare); 6769 6770 if ((rc = mdb_dbi_open(txn, "note_blocks", MDB_CREATE | MDB_INTEGERKEY, 6771 &lmdb->dbs[NDB_DB_NOTE_BLOCKS]))) { 6772 fprintf(stderr, "mdb_dbi_open note_blocks failed: %s\n", mdb_strerror(rc)); 6773 return 0; 6774 } 6775 6776 if ((rc = mdb_dbi_open(txn, "note_tags", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, 6777 &lmdb->dbs[NDB_DB_NOTE_TAGS]))) { 6778 fprintf(stderr, "mdb_dbi_open note_tags failed: %s\n", mdb_strerror(rc)); 6779 return 0; 6780 } 6781 mdb_set_compare(txn, lmdb->dbs[NDB_DB_NOTE_TAGS], ndb_tag_key_compare); 6782 6783 // Commit the transaction 6784 if ((rc = mdb_txn_commit(txn))) { 6785 fprintf(stderr, "mdb_txn_commit failed, error %d\n", rc); 6786 return 0; 6787 } 6788 6789 return 1; 6790 } 6791 6792 static int ndb_queue_write_version(struct ndb *ndb, uint64_t version) 6793 { 6794 struct ndb_writer_msg msg; 6795 msg.type = NDB_WRITER_DBMETA; 6796 msg.ndb_meta.version = version; 6797 return ndb_writer_queue_msg(&ndb->writer, &msg); 6798 } 6799 6800 static void ndb_monitor_init(struct ndb_monitor *monitor, ndb_sub_fn cb, 6801 void *sub_cb_ctx) 6802 { 6803 monitor->num_subscriptions = 0; 6804 monitor->sub_cb = cb; 6805 monitor->sub_cb_ctx = sub_cb_ctx; 6806 pthread_mutex_init(&monitor->mutex, NULL); 6807 } 6808 6809 void ndb_filter_group_destroy(struct ndb_filter_group *group) 6810 { 6811 struct ndb_filter *filter; 6812 int i; 6813 for (i = 0; i < group->num_filters; i++) { 6814 filter = &group->filters[i]; 6815 ndb_filter_destroy(filter); 6816 } 6817 } 6818 6819 static void ndb_subscription_destroy(struct ndb_subscription *sub) 6820 { 6821 ndb_filter_group_destroy(&sub->group); 6822 // this was malloc'd inside ndb_subscribe 6823 free(sub->inbox.buf); 6824 prot_queue_destroy(&sub->inbox); 6825 sub->subid = 0; 6826 } 6827 6828 static void ndb_monitor_destroy(struct ndb_monitor *monitor) 6829 { 6830 int i; 6831 6832 ndb_monitor_lock(monitor); 6833 6834 for (i = 0; i < monitor->num_subscriptions; i++) { 6835 ndb_subscription_destroy(&monitor->subscriptions[i]); 6836 } 6837 6838 monitor->num_subscriptions = 0; 6839 6840 ndb_monitor_unlock(monitor); 6841 6842 pthread_mutex_destroy(&monitor->mutex); 6843 } 6844 6845 int ndb_init(struct ndb **pndb, const char *filename, const struct ndb_config *config) 6846 { 6847 struct ndb *ndb; 6848 //MDB_dbi ind_id; // TODO: ind_pk, etc 6849 6850 ndb = *pndb = calloc(1, sizeof(struct ndb)); 6851 ndb->flags = config->flags; 6852 6853 if (ndb == NULL) { 6854 fprintf(stderr, "ndb_init: malloc failed\n"); 6855 return 0; 6856 } 6857 6858 if (!ndb_init_lmdb(filename, &ndb->lmdb, config->mapsize)) 6859 return 0; 6860 6861 ndb_monitor_init(&ndb->monitor, config->sub_cb, config->sub_cb_ctx); 6862 6863 if (!ndb_writer_init(&ndb->writer, &ndb->lmdb, &ndb->monitor, ndb->flags, 6864 config->writer_scratch_buffer_size)) { 6865 fprintf(stderr, "ndb_writer_init failed\n"); 6866 return 0; 6867 } 6868 6869 if (!ndb_ingester_init(&ndb->ingester, &ndb->lmdb, &ndb->writer.inbox, 6870 config->writer_scratch_buffer_size, config)) { 6871 fprintf(stderr, "failed to initialize %d ingester thread(s)\n", 6872 config->ingester_threads); 6873 return 0; 6874 } 6875 6876 if (!ndb_flag_set(config->flags, NDB_FLAG_NOMIGRATE)) { 6877 struct ndb_writer_msg msg = { .type = NDB_WRITER_MIGRATE }; 6878 ndb_writer_queue_msg(&ndb->writer, &msg); 6879 } 6880 6881 // Initialize LMDB environment and spin up threads 6882 return 1; 6883 } 6884 6885 void ndb_destroy(struct ndb *ndb) 6886 { 6887 if (ndb == NULL) 6888 return; 6889 6890 // ingester depends on writer and must be destroyed first 6891 ndb_debug("destroying ingester\n"); 6892 ndb_ingester_destroy(&ndb->ingester); 6893 ndb_debug("destroying writer\n"); 6894 ndb_writer_destroy(&ndb->writer); 6895 ndb_debug("destroying monitor\n"); 6896 ndb_monitor_destroy(&ndb->monitor); 6897 6898 ndb_debug("closing env\n"); 6899 mdb_env_close(ndb->lmdb.env); 6900 6901 ndb_debug("ndb destroyed\n"); 6902 free(ndb); 6903 } 6904 6905 6906 // Process a nostr event from a client 6907 // 6908 // ie: ["EVENT", {"content":"..."} ...] 6909 // 6910 // The client-sent variation of ndb_process_event 6911 int ndb_process_client_event(struct ndb *ndb, const char *json, int len) 6912 { 6913 struct ndb_ingest_meta meta; 6914 ndb_ingest_meta_init(&meta, 1, NULL); 6915 6916 return ndb_ingest_event(&ndb->ingester, json, len, &meta); 6917 } 6918 6919 // Process anostr event from a relay, 6920 // 6921 // ie: ["EVENT", "subid", {"content":"..."}...] 6922 // 6923 // This function returns as soon as possible, first copying the passed 6924 // json and then queueing it up for processing. Worker threads then take 6925 // the json and process it. 6926 // 6927 // Processing: 6928 // 6929 // 1. The event is parsed into ndb_notes and the signature is validated 6930 // 2. A quick lookup is made on the database to see if we already have 6931 // the note id, if we do we don't need to waste time on json parsing 6932 // or note validation. 6933 // 3. Once validation is done we pass it to the writer queue for writing 6934 // to LMDB. 6935 // 6936 int ndb_process_event(struct ndb *ndb, const char *json, int json_len) 6937 { 6938 struct ndb_ingest_meta meta; 6939 ndb_ingest_meta_init(&meta, 0, NULL); 6940 6941 return ndb_ingest_event(&ndb->ingester, json, json_len, &meta); 6942 } 6943 6944 int ndb_process_event_with(struct ndb *ndb, const char *json, int json_len, 6945 struct ndb_ingest_meta *meta) 6946 { 6947 return ndb_ingest_event(&ndb->ingester, json, json_len, meta); 6948 } 6949 6950 int _ndb_process_events(struct ndb *ndb, const char *ldjson, size_t json_len, 6951 struct ndb_ingest_meta *meta) 6952 { 6953 const char *start, *end, *very_end; 6954 start = ldjson; 6955 end = start + json_len; 6956 very_end = ldjson + json_len; 6957 #if DEBUG 6958 int processed = 0; 6959 #endif 6960 6961 while ((end = fast_strchr(start, '\n', very_end - start))) { 6962 //printf("processing '%.*s'\n", (int)(end-start), start); 6963 if (!ndb_process_event_with(ndb, start, end - start, meta)) { 6964 ndb_debug("ndb_process_client_event failed\n"); 6965 return 0; 6966 } 6967 start = end + 1; 6968 #if DEBUG 6969 processed++; 6970 #endif 6971 } 6972 6973 #if DEBUG 6974 ndb_debug("ndb_process_events: processed %d events\n", processed); 6975 #endif 6976 6977 return 1; 6978 } 6979 6980 #ifndef _WIN32 6981 // TODO: windows 6982 int ndb_process_events_stream(struct ndb *ndb, FILE* fp) 6983 { 6984 char *line = NULL; 6985 size_t len = 0; 6986 ssize_t nread; 6987 6988 while ((nread = getline(&line, &len, fp)) != -1) { 6989 if (line == NULL) 6990 break; 6991 ndb_process_event(ndb, line, len); 6992 } 6993 6994 if (line) 6995 free(line); 6996 6997 return 1; 6998 } 6999 #endif 7000 7001 int ndb_process_events_with(struct ndb *ndb, const char *ldjson, size_t json_len, 7002 struct ndb_ingest_meta *meta) 7003 { 7004 return _ndb_process_events(ndb, ldjson, json_len, meta); 7005 } 7006 7007 int ndb_process_client_events(struct ndb *ndb, const char *ldjson, size_t json_len) 7008 { 7009 struct ndb_ingest_meta meta; 7010 ndb_ingest_meta_init(&meta, 1, NULL); 7011 7012 return _ndb_process_events(ndb, ldjson, json_len, &meta); 7013 } 7014 7015 int ndb_process_events(struct ndb *ndb, const char *ldjson, size_t json_len) 7016 { 7017 struct ndb_ingest_meta meta; 7018 ndb_ingest_meta_init(&meta, 0, NULL); 7019 7020 return _ndb_process_events(ndb, ldjson, json_len, &meta); 7021 } 7022 7023 static inline int cursor_push_tag(struct cursor *cur, struct ndb_tag *tag) 7024 { 7025 return cursor_push_u16(cur, tag->count); 7026 } 7027 7028 int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf, 7029 size_t bufsize) 7030 { 7031 struct ndb_note *note; 7032 int half, size, str_indices_size; 7033 7034 // come on bruh 7035 if (bufsize < sizeof(struct ndb_note) * 2) 7036 return 0; 7037 7038 str_indices_size = bufsize / 32; 7039 size = bufsize - str_indices_size; 7040 half = size / 2; 7041 7042 //debug("size %d half %d str_indices %d\n", size, half, str_indices_size); 7043 7044 // make a safe cursor of our available memory 7045 make_cursor(buf, buf + bufsize, &builder->mem); 7046 7047 note = builder->note = (struct ndb_note *)buf; 7048 7049 // take slices of the memory into subcursors 7050 if (!(cursor_slice(&builder->mem, &builder->note_cur, half) && 7051 cursor_slice(&builder->mem, &builder->strings, half) && 7052 cursor_slice(&builder->mem, &builder->str_indices, str_indices_size))) { 7053 return 0; 7054 } 7055 7056 memset(note, 0, sizeof(*note)); 7057 builder->note_cur.p += sizeof(*note); 7058 7059 note->strings = builder->strings.start - buf; 7060 note->version = 1; 7061 7062 return 1; 7063 } 7064 7065 7066 7067 static inline int ndb_json_parser_init(struct ndb_json_parser *p, 7068 const char *json, int json_len, 7069 unsigned char *buf, int bufsize) 7070 { 7071 int half = bufsize / 2; 7072 7073 unsigned char *tok_start = buf + half; 7074 unsigned char *tok_end = buf + bufsize; 7075 7076 p->toks = (jsmntok_t*)tok_start; 7077 p->toks_end = (jsmntok_t*)tok_end; 7078 p->num_tokens = 0; 7079 p->json = json; 7080 p->json_len = json_len; 7081 7082 // ndb_builder gets the first half of the buffer, and jsmn gets the 7083 // second half. I like this way of alloating memory (without actually 7084 // dynamically allocating memory). You get one big chunk upfront and 7085 // then submodules can recursively subdivide it. Maybe you could do 7086 // something even more clever like golden-ratio style subdivision where 7087 // the more important stuff gets a larger chunk and then it spirals 7088 // downward into smaller chunks. Thanks for coming to my TED talk. 7089 7090 if (!ndb_builder_init(&p->builder, buf, half)) 7091 return 0; 7092 7093 jsmn_init(&p->json_parser); 7094 7095 return 1; 7096 } 7097 7098 static inline int ndb_json_parser_parse(struct ndb_json_parser *p, 7099 struct ndb_id_cb *cb) 7100 { 7101 jsmntok_t *tok; 7102 int cap = ((unsigned char *)p->toks_end - (unsigned char*)p->toks)/sizeof(*p->toks); 7103 int res = 7104 jsmn_parse(&p->json_parser, p->json, p->json_len, p->toks, cap, cb != NULL); 7105 7106 // got an ID! 7107 if (res == -42) { 7108 tok = &p->toks[p->json_parser.toknext-1]; 7109 7110 switch (cb->fn(cb->data, p->json + tok->start)) { 7111 case NDB_IDRES_CONT: 7112 res = jsmn_parse(&p->json_parser, p->json, p->json_len, 7113 p->toks, cap, 0); 7114 break; 7115 case NDB_IDRES_STOP: 7116 return -42; 7117 } 7118 } else if (res == 0) { 7119 return 0; 7120 } 7121 7122 p->num_tokens = res; 7123 p->i = 0; 7124 7125 return 1; 7126 } 7127 7128 static inline int toksize(jsmntok_t *tok) 7129 { 7130 return tok->end - tok->start; 7131 } 7132 7133 7134 7135 static int cursor_push_unescaped_char(struct cursor *cur, char c1, char c2) 7136 { 7137 switch (c2) { 7138 case 't': return cursor_push_byte(cur, '\t'); 7139 case 'n': return cursor_push_byte(cur, '\n'); 7140 case 'r': return cursor_push_byte(cur, '\r'); 7141 case 'b': return cursor_push_byte(cur, '\b'); 7142 case 'f': return cursor_push_byte(cur, '\f'); 7143 case '\\': return cursor_push_byte(cur, '\\'); 7144 case '/': return cursor_push_byte(cur, '/'); 7145 case '"': return cursor_push_byte(cur, '"'); 7146 case 'u': 7147 // these aren't handled yet 7148 return 0; 7149 default: 7150 return cursor_push_byte(cur, c1) && cursor_push_byte(cur, c2); 7151 } 7152 } 7153 7154 static int cursor_push_escaped_char(struct cursor *cur, char c) 7155 { 7156 switch (c) { 7157 case '"': return cursor_push_str(cur, "\\\""); 7158 case '\\': return cursor_push_str(cur, "\\\\"); 7159 case '\b': return cursor_push_str(cur, "\\b"); 7160 case '\f': return cursor_push_str(cur, "\\f"); 7161 case '\n': return cursor_push_str(cur, "\\n"); 7162 case '\r': return cursor_push_str(cur, "\\r"); 7163 case '\t': return cursor_push_str(cur, "\\t"); 7164 // TODO: \u hex hex hex hex 7165 } 7166 return cursor_push_byte(cur, c); 7167 } 7168 7169 static int cursor_push_hex_str(struct cursor *cur, unsigned char *buf, int len) 7170 { 7171 int i; 7172 7173 if (len % 2 != 0) 7174 return 0; 7175 7176 if (!cursor_push_byte(cur, '"')) 7177 return 0; 7178 7179 for (i = 0; i < len; i++) { 7180 unsigned int c = ((const unsigned char *)buf)[i]; 7181 if (!cursor_push_byte(cur, hexchar(c >> 4))) 7182 return 0; 7183 if (!cursor_push_byte(cur, hexchar(c & 0xF))) 7184 return 0; 7185 } 7186 7187 if (!cursor_push_byte(cur, '"')) 7188 return 0; 7189 7190 return 1; 7191 } 7192 7193 static int cursor_push_jsonstr(struct cursor *cur, const char *str) 7194 { 7195 int i; 7196 int len; 7197 7198 len = strlen(str); 7199 7200 if (!cursor_push_byte(cur, '"')) 7201 return 0; 7202 7203 for (i = 0; i < len; i++) { 7204 if (!cursor_push_escaped_char(cur, str[i])) 7205 return 0; 7206 } 7207 7208 if (!cursor_push_byte(cur, '"')) 7209 return 0; 7210 7211 return 1; 7212 } 7213 7214 7215 static inline int cursor_push_json_tag_str(struct cursor *cur, struct ndb_str str) 7216 { 7217 if (str.flag == NDB_PACKED_ID) 7218 return cursor_push_hex_str(cur, str.id, 32); 7219 7220 return cursor_push_jsonstr(cur, str.str); 7221 } 7222 7223 static int cursor_push_json_tag(struct cursor *cur, struct ndb_note *note, 7224 struct ndb_tag *tag) 7225 { 7226 int i; 7227 7228 if (!cursor_push_byte(cur, '[')) 7229 return 0; 7230 7231 for (i = 0; i < tag->count; i++) { 7232 if (!cursor_push_json_tag_str(cur, ndb_tag_str(note, tag, i))) 7233 return 0; 7234 if (i != tag->count-1 && !cursor_push_byte(cur, ',')) 7235 return 0; 7236 } 7237 7238 return cursor_push_byte(cur, ']'); 7239 } 7240 7241 static int cursor_push_json_tags(struct cursor *cur, struct ndb_note *note) 7242 { 7243 int i; 7244 struct ndb_iterator iter, *it = &iter; 7245 ndb_tags_iterate_start(note, it); 7246 7247 if (!cursor_push_byte(cur, '[')) 7248 return 0; 7249 7250 i = 0; 7251 while (ndb_tags_iterate_next(it)) { 7252 if (!cursor_push_json_tag(cur, note, it->tag)) 7253 return 0; 7254 if (i != note->tags.count-1 && !cursor_push_str(cur, ",")) 7255 return 0; 7256 i++; 7257 } 7258 7259 if (!cursor_push_byte(cur, ']')) 7260 return 0; 7261 7262 return 1; 7263 } 7264 7265 static int ndb_event_commitment(struct ndb_note *ev, unsigned char *buf, int buflen) 7266 { 7267 char timebuf[16] = {0}; 7268 char kindbuf[16] = {0}; 7269 char pubkey[65]; 7270 struct cursor cur; 7271 int ok; 7272 7273 if (!hex_encode(ev->pubkey, sizeof(ev->pubkey), pubkey)) 7274 return 0; 7275 7276 make_cursor(buf, buf + buflen, &cur); 7277 7278 // TODO: update in 2106 ... 7279 snprintf(timebuf, sizeof(timebuf), "%d", (uint32_t)ev->created_at); 7280 snprintf(kindbuf, sizeof(kindbuf), "%d", ev->kind); 7281 7282 ok = 7283 cursor_push_str(&cur, "[0,\"") && 7284 cursor_push_str(&cur, pubkey) && 7285 cursor_push_str(&cur, "\",") && 7286 cursor_push_str(&cur, timebuf) && 7287 cursor_push_str(&cur, ",") && 7288 cursor_push_str(&cur, kindbuf) && 7289 cursor_push_str(&cur, ",") && 7290 cursor_push_json_tags(&cur, ev) && 7291 cursor_push_str(&cur, ",") && 7292 cursor_push_jsonstr(&cur, ndb_note_str(ev, &ev->content).str) && 7293 cursor_push_str(&cur, "]"); 7294 7295 if (!ok) 7296 return 0; 7297 7298 return cur.p - cur.start; 7299 } 7300 7301 static int cursor_push_hex(struct cursor *c, unsigned char *bytes, int len) 7302 { 7303 int i; 7304 unsigned char chr; 7305 if (c->p + (len * 2) >= c->end) 7306 return 0; 7307 7308 for (i = 0; i < len; i++) { 7309 chr = bytes[i]; 7310 7311 *(c->p++) = hexchar(chr >> 4); 7312 *(c->p++) = hexchar(chr & 0xF); 7313 } 7314 7315 return 1; 7316 } 7317 7318 static int cursor_push_int_str(struct cursor *c, uint64_t num) 7319 { 7320 char timebuf[16] = {0}; 7321 snprintf(timebuf, sizeof(timebuf), "%" PRIu64, num); 7322 return cursor_push_str(c, timebuf); 7323 } 7324 7325 int ndb_note_json(struct ndb_note *note, char *buf, int buflen) 7326 { 7327 struct cursor cur, *c = &cur; 7328 7329 make_cursor((unsigned char *)buf, (unsigned char*)buf + buflen, &cur); 7330 7331 int ok = cursor_push_str(c, "{\"id\":\"") && 7332 cursor_push_hex(c, ndb_note_id(note), 32) && 7333 cursor_push_str(c, "\",\"pubkey\":\"") && 7334 cursor_push_hex(c, ndb_note_pubkey(note), 32) && 7335 cursor_push_str(c, "\",\"created_at\":") && 7336 cursor_push_int_str(c, ndb_note_created_at(note)) && 7337 cursor_push_str(c, ",\"kind\":") && 7338 cursor_push_int_str(c, ndb_note_kind(note)) && 7339 cursor_push_str(c, ",\"tags\":") && 7340 cursor_push_json_tags(c, note) && 7341 cursor_push_str(c, ",\"content\":") && 7342 cursor_push_jsonstr(c, ndb_note_content(note)) && 7343 cursor_push_str(c, ",\"sig\":\"") && 7344 cursor_push_hex(c, ndb_note_sig(note), 64) && 7345 cursor_push_c_str(c, "\"}"); 7346 7347 if (!ok) { 7348 return 0; 7349 } 7350 7351 return cur.p - cur.start; 7352 } 7353 7354 static int cursor_push_json_elem_array(struct cursor *cur, 7355 const struct ndb_filter *filter, 7356 struct ndb_filter_elements *elems) 7357 { 7358 int i; 7359 unsigned char *id; 7360 const char *str; 7361 uint64_t val; 7362 7363 if (!cursor_push_byte(cur, '[')) 7364 return 0; 7365 7366 for (i = 0; i < elems->count; i++) { 7367 7368 switch (elems->field.elem_type) { 7369 case NDB_ELEMENT_CUSTOM: 7370 // can't serialize custom functions 7371 break; 7372 case NDB_ELEMENT_STRING: 7373 str = ndb_filter_get_string_element(filter, elems, i); 7374 if (!cursor_push_jsonstr(cur, str)) 7375 return 0; 7376 break; 7377 case NDB_ELEMENT_ID: 7378 id = ndb_filter_get_id_element(filter, elems, i); 7379 if (!cursor_push_hex_str(cur, id, 32)) 7380 return 0; 7381 break; 7382 case NDB_ELEMENT_INT: 7383 val = ndb_filter_get_int_element(elems, i); 7384 if (!cursor_push_int_str(cur, val)) 7385 return 0; 7386 break; 7387 case NDB_ELEMENT_UNKNOWN: 7388 ndb_debug("unknown element in cursor_push_json_elem_array"); 7389 return 0; 7390 } 7391 7392 if (i != elems->count-1) { 7393 if (!cursor_push_byte(cur, ',')) 7394 return 0; 7395 } 7396 } 7397 7398 if (!cursor_push_str(cur, "]")) 7399 return 0; 7400 7401 return 1; 7402 } 7403 7404 int ndb_filter_json(const struct ndb_filter *filter, char *buf, int buflen) 7405 { 7406 const char *str; 7407 struct cursor cur, *c = &cur; 7408 struct ndb_filter_elements *elems; 7409 int i; 7410 7411 if (!filter->finalized) { 7412 ndb_debug("filter not finalized in ndb_filter_json\n"); 7413 return 0; 7414 } 7415 7416 make_cursor((unsigned char *)buf, (unsigned char*)buf + buflen, c); 7417 7418 if (!cursor_push_str(c, "{")) 7419 return 0; 7420 7421 for (i = 0; i < filter->num_elements; i++) { 7422 elems = ndb_filter_get_elements(filter, i); 7423 switch (elems->field.type) { 7424 case NDB_FILTER_CUSTOM: 7425 // nothing to encode these as 7426 break; 7427 case NDB_FILTER_IDS: 7428 if (!cursor_push_str(c, "\"ids\":")) 7429 return 0; 7430 if (!cursor_push_json_elem_array(c, filter, elems)) 7431 return 0; 7432 break; 7433 case NDB_FILTER_SEARCH: 7434 if (!cursor_push_str(c, "\"search\":")) 7435 return 0; 7436 if (!(str = ndb_filter_get_string_element(filter, elems, 0))) 7437 return 0; 7438 if (!cursor_push_jsonstr(c, str)) 7439 return 0; 7440 break; 7441 case NDB_FILTER_AUTHORS: 7442 if (!cursor_push_str(c, "\"authors\":")) 7443 return 0; 7444 if (!cursor_push_json_elem_array(c, filter, elems)) 7445 return 0; 7446 break; 7447 case NDB_FILTER_KINDS: 7448 if (!cursor_push_str(c, "\"kinds\":")) 7449 return 0; 7450 if (!cursor_push_json_elem_array(c, filter, elems)) 7451 return 0; 7452 break; 7453 case NDB_FILTER_TAGS: 7454 if (!cursor_push_str(c, "\"#")) 7455 return 0; 7456 if (!cursor_push_byte(c, elems->field.tag)) 7457 return 0; 7458 if (!cursor_push_str(c, "\":")) 7459 return 0; 7460 if (!cursor_push_json_elem_array(c, filter, elems)) 7461 return 0; 7462 break; 7463 case NDB_FILTER_SINCE: 7464 if (!cursor_push_str(c, "\"since\":")) 7465 return 0; 7466 if (!cursor_push_int_str(c, ndb_filter_get_int_element(elems, 0))) 7467 return 0; 7468 break; 7469 case NDB_FILTER_UNTIL: 7470 if (!cursor_push_str(c, "\"until\":")) 7471 return 0; 7472 if (!cursor_push_int_str(c, ndb_filter_get_int_element(elems, 0))) 7473 return 0; 7474 break; 7475 case NDB_FILTER_LIMIT: 7476 if (!cursor_push_str(c, "\"limit\":")) 7477 return 0; 7478 if (!cursor_push_int_str(c, ndb_filter_get_int_element(elems, 0))) 7479 return 0; 7480 break; 7481 case NDB_FILTER_RELAYS: 7482 if (!cursor_push_str(c, "\"relays\":")) 7483 return 0; 7484 if (!cursor_push_json_elem_array(c, filter, elems)) 7485 return 0; 7486 break; 7487 } 7488 7489 if (i != filter->num_elements-1) { 7490 if (!cursor_push_byte(c, ',')) { 7491 return 0; 7492 } 7493 } 7494 7495 } 7496 7497 if (!cursor_push_c_str(c, "}")) 7498 return 0; 7499 7500 return cur.p - cur.start; 7501 } 7502 7503 int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen, unsigned char *id) { 7504 int len; 7505 7506 if (!(len = ndb_event_commitment(note, buf, buflen))) 7507 return 0; 7508 7509 //fprintf(stderr, "%.*s\n", len, buf); 7510 7511 sha256((struct sha256*)id, buf, len); 7512 7513 return 1; 7514 } 7515 7516 int ndb_sign_id(struct ndb_keypair *keypair, unsigned char id[32], 7517 unsigned char sig[64]) 7518 { 7519 unsigned char aux[32]; 7520 secp256k1_keypair *pair = (secp256k1_keypair*) keypair->pair; 7521 7522 if (!fill_random(aux, sizeof(aux))) 7523 return 0; 7524 7525 secp256k1_context *ctx = 7526 secp256k1_context_create(SECP256K1_CONTEXT_NONE); 7527 7528 return secp256k1_schnorrsig_sign32(ctx, sig, id, pair, aux); 7529 } 7530 7531 int ndb_create_keypair(struct ndb_keypair *kp) 7532 { 7533 secp256k1_keypair *keypair = (secp256k1_keypair*)kp->pair; 7534 secp256k1_xonly_pubkey pubkey; 7535 7536 secp256k1_context *ctx = 7537 secp256k1_context_create(SECP256K1_CONTEXT_NONE);; 7538 7539 /* Try to create a keypair with a valid context, it should only 7540 * fail if the secret key is zero or out of range. */ 7541 if (!secp256k1_keypair_create(ctx, keypair, kp->secret)) 7542 return 0; 7543 7544 if (!secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, keypair)) 7545 return 0; 7546 7547 /* Serialize the public key. Should always return 1 for a valid public key. */ 7548 return secp256k1_xonly_pubkey_serialize(ctx, kp->pubkey, &pubkey); 7549 } 7550 7551 int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair) 7552 { 7553 if (!hex_decode(secstr, strlen(secstr), keypair->secret, 32)) { 7554 fprintf(stderr, "could not hex decode secret key\n"); 7555 return 0; 7556 } 7557 7558 return ndb_create_keypair(keypair); 7559 } 7560 7561 int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note, 7562 struct ndb_keypair *keypair) 7563 { 7564 int strings_len = builder->strings.p - builder->strings.start; 7565 unsigned char *note_end = builder->note_cur.p + strings_len; 7566 int total_size = note_end - builder->note_cur.start; 7567 7568 // move the strings buffer next to the end of our ndb_note 7569 memmove(builder->note_cur.p, builder->strings.start, strings_len); 7570 7571 // set the strings location 7572 builder->note->strings = builder->note_cur.p - builder->note_cur.start; 7573 7574 // record the total size 7575 //builder->note->size = total_size; 7576 7577 *note = builder->note; 7578 7579 // generate id and sign if we're building this manually 7580 if (keypair) { 7581 // use the remaining memory for building our id buffer 7582 unsigned char *end = builder->mem.end; 7583 unsigned char *start = (unsigned char*)(*note) + total_size; 7584 7585 ndb_builder_set_pubkey(builder, keypair->pubkey); 7586 7587 if (!ndb_calculate_id(builder->note, start, end - start, 7588 builder->note->id)) 7589 return 0; 7590 7591 if (!ndb_sign_id(keypair, (*note)->id, (*note)->sig)) 7592 return 0; 7593 } 7594 7595 // make sure we're aligned as a whole 7596 total_size = (total_size + 7) & ~7; 7597 assert((total_size % 8) == 0); 7598 return total_size; 7599 } 7600 7601 struct ndb_note * ndb_builder_note(struct ndb_builder *builder) 7602 { 7603 return builder->note; 7604 } 7605 7606 static union ndb_packed_str ndb_offset_str(uint32_t offset) 7607 { 7608 // ensure accidents like -1 don't corrupt our packed_str 7609 union ndb_packed_str str; 7610 // most significant byte is reserved for ndb_packtype 7611 str.offset = offset & 0xFFFFFF; 7612 return str; 7613 } 7614 7615 7616 /// find an existing string via str_indices. these indices only exist in the 7617 /// builder phase just for this purpose. 7618 static inline int ndb_builder_find_str(struct ndb_builder *builder, 7619 const char *str, int len, 7620 union ndb_packed_str *pstr) 7621 { 7622 // find existing matching string to avoid duplicate strings 7623 int indices = cursor_count(&builder->str_indices, sizeof(uint32_t)); 7624 for (int i = 0; i < indices; i++) { 7625 uint32_t index = ((uint32_t*)builder->str_indices.start)[i]; 7626 const char *some_str = (const char*)builder->strings.start + index; 7627 7628 if (!memcmp(some_str, str, len) && some_str[len] == '\0') { 7629 // found an existing matching str, use that index 7630 *pstr = ndb_offset_str(index); 7631 return 1; 7632 } 7633 } 7634 7635 return 0; 7636 } 7637 7638 static int ndb_builder_push_str(struct ndb_builder *builder, const char *str, 7639 int len, union ndb_packed_str *pstr) 7640 { 7641 uint32_t loc; 7642 7643 // no string found, push a new one 7644 loc = builder->strings.p - builder->strings.start; 7645 if (!(cursor_push(&builder->strings, (unsigned char*)str, len) && 7646 cursor_push_byte(&builder->strings, '\0'))) { 7647 return 0; 7648 } 7649 7650 *pstr = ndb_offset_str(loc); 7651 7652 // record in builder indices. ignore return value, if we can't cache it 7653 // then whatever 7654 cursor_push_u32(&builder->str_indices, loc); 7655 7656 return 1; 7657 } 7658 7659 static int ndb_builder_push_packed_id(struct ndb_builder *builder, 7660 unsigned char *id, 7661 union ndb_packed_str *pstr) 7662 { 7663 // Don't both find id duplicates. very rarely are they duplicated 7664 // and it slows things down quite a bit. If we really care about this 7665 // We can switch to a hash table. 7666 //if (ndb_builder_find_str(builder, (const char*)id, 32, pstr)) { 7667 // pstr->packed.flag = NDB_PACKED_ID; 7668 // return 1; 7669 //} 7670 7671 if (ndb_builder_push_str(builder, (const char*)id, 32, pstr)) { 7672 pstr->packed.flag = NDB_PACKED_ID; 7673 return 1; 7674 } 7675 7676 return 0; 7677 } 7678 7679 union ndb_packed_str ndb_chars_to_packed_str(char c1, char c2) 7680 { 7681 union ndb_packed_str str; 7682 str.packed.flag = NDB_PACKED_STR; 7683 str.packed.str[0] = c1; 7684 str.packed.str[1] = c2; 7685 str.packed.str[2] = '\0'; 7686 return str; 7687 } 7688 7689 static union ndb_packed_str ndb_char_to_packed_str(char c) 7690 { 7691 union ndb_packed_str str; 7692 str.packed.flag = NDB_PACKED_STR; 7693 str.packed.str[0] = c; 7694 str.packed.str[1] = '\0'; 7695 return str; 7696 } 7697 7698 7699 /// Check for small strings to pack 7700 static inline int ndb_builder_try_compact_str(struct ndb_builder *builder, 7701 const char *str, int len, 7702 union ndb_packed_str *pstr, 7703 int pack_ids) 7704 { 7705 unsigned char id_buf[32]; 7706 7707 if (len == 0) { 7708 *pstr = ndb_char_to_packed_str(0); 7709 return 1; 7710 } else if (len == 1) { 7711 *pstr = ndb_char_to_packed_str(str[0]); 7712 return 1; 7713 } else if (len == 2) { 7714 *pstr = ndb_chars_to_packed_str(str[0], str[1]); 7715 return 1; 7716 } else if (pack_ids && len == 64 && hex_decode(str, 64, id_buf, 32)) { 7717 return ndb_builder_push_packed_id(builder, id_buf, pstr); 7718 } 7719 7720 return 0; 7721 } 7722 7723 7724 static int ndb_builder_push_unpacked_str(struct ndb_builder *builder, 7725 const char *str, int len, 7726 union ndb_packed_str *pstr) 7727 { 7728 if (ndb_builder_find_str(builder, str, len, pstr)) 7729 return 1; 7730 7731 return ndb_builder_push_str(builder, str, len, pstr); 7732 } 7733 7734 int ndb_builder_make_str(struct ndb_builder *builder, const char *str, int len, 7735 union ndb_packed_str *pstr, int pack_ids) 7736 { 7737 if (ndb_builder_try_compact_str(builder, str, len, pstr, pack_ids)) 7738 return 1; 7739 7740 return ndb_builder_push_unpacked_str(builder, str, len, pstr); 7741 } 7742 7743 int ndb_builder_set_content(struct ndb_builder *builder, const char *content, 7744 int len) 7745 { 7746 int pack_ids = 0; 7747 builder->note->content_length = len; 7748 return ndb_builder_make_str(builder, content, len, 7749 &builder->note->content, pack_ids); 7750 } 7751 7752 7753 static inline int jsoneq(const char *json, jsmntok_t *tok, int tok_len, 7754 const char *s) 7755 { 7756 if (tok->type == JSMN_STRING && (int)strlen(s) == tok_len && 7757 memcmp(json + tok->start, s, tok_len) == 0) { 7758 return 1; 7759 } 7760 return 0; 7761 } 7762 7763 static int ndb_builder_finalize_tag(struct ndb_builder *builder, 7764 union ndb_packed_str offset) 7765 { 7766 if (!cursor_push_u32(&builder->note_cur, offset.offset)) 7767 return 0; 7768 builder->current_tag->count++; 7769 return 1; 7770 } 7771 7772 /// Unescape and push json strings 7773 static int ndb_builder_make_json_str(struct ndb_builder *builder, 7774 const char *str, int len, 7775 union ndb_packed_str *pstr, 7776 int *written, int pack_ids) 7777 { 7778 // let's not care about de-duping these. we should just unescape 7779 // in-place directly into the strings table. 7780 if (written) 7781 *written = len; 7782 7783 const char *p, *end, *start; 7784 unsigned char *builder_start; 7785 7786 // always try compact strings first 7787 if (ndb_builder_try_compact_str(builder, str, len, pstr, pack_ids)) 7788 return 1; 7789 7790 end = str + len; 7791 start = str; // Initialize start to the beginning of the string 7792 7793 *pstr = ndb_offset_str(builder->strings.p - builder->strings.start); 7794 builder_start = builder->strings.p; 7795 7796 for (p = str; p < end; p++) { 7797 if (*p == '\\' && p+1 < end) { 7798 // Push the chunk of unescaped characters before this escape sequence 7799 if (start < p && !cursor_push(&builder->strings, 7800 (unsigned char *)start, 7801 p - start)) { 7802 return 0; 7803 } 7804 7805 if (!cursor_push_unescaped_char(&builder->strings, *p, *(p+1))) 7806 return 0; 7807 7808 p++; // Skip the character following the backslash 7809 start = p + 1; // Update the start pointer to the next character 7810 } 7811 } 7812 7813 // Handle the last chunk after the last escape sequence (or if there are no escape sequences at all) 7814 if (start < p && !cursor_push(&builder->strings, (unsigned char *)start, 7815 p - start)) { 7816 return 0; 7817 } 7818 7819 if (written) 7820 *written = builder->strings.p - builder_start; 7821 7822 // TODO: dedupe these!? 7823 return cursor_push_byte(&builder->strings, '\0'); 7824 } 7825 7826 static int ndb_builder_push_json_tag(struct ndb_builder *builder, 7827 const char *str, int len) 7828 { 7829 union ndb_packed_str pstr; 7830 int pack_ids = 1; 7831 if (!ndb_builder_make_json_str(builder, str, len, &pstr, NULL, pack_ids)) 7832 return 0; 7833 return ndb_builder_finalize_tag(builder, pstr); 7834 } 7835 7836 // Push a json array into an ndb tag ["p", "abcd..."] -> struct ndb_tag 7837 static int ndb_builder_tag_from_json_array(struct ndb_json_parser *p, 7838 jsmntok_t *array) 7839 { 7840 jsmntok_t *str_tok; 7841 const char *str; 7842 7843 if (array->size == 0) 7844 return 0; 7845 7846 if (!ndb_builder_new_tag(&p->builder)) 7847 return 0; 7848 7849 for (int i = 0; i < array->size; i++) { 7850 str_tok = &array[i+1]; 7851 str = p->json + str_tok->start; 7852 7853 if (!ndb_builder_push_json_tag(&p->builder, str, 7854 toksize(str_tok))) { 7855 return 0; 7856 } 7857 } 7858 7859 return 1; 7860 } 7861 7862 // Push json tags into ndb data 7863 // [["t", "hashtag"], ["p", "abcde..."]] -> struct ndb_tags 7864 static inline int ndb_builder_process_json_tags(struct ndb_json_parser *p, 7865 jsmntok_t *array) 7866 { 7867 jsmntok_t *tag = array; 7868 7869 if (array->size == 0) 7870 return 1; 7871 7872 for (int i = 0; i < array->size; i++) { 7873 if (!ndb_builder_tag_from_json_array(p, &tag[i+1])) 7874 return 0; 7875 tag += tag[i+1].size; 7876 } 7877 7878 return 1; 7879 } 7880 7881 static int parse_unsigned_int(const char *start, int len, unsigned int *num) 7882 { 7883 unsigned int number = 0; 7884 const char *p = start, *end = start + len; 7885 int digits = 0; 7886 7887 while (p < end) { 7888 char c = *p; 7889 7890 if (c < '0' || c > '9') 7891 break; 7892 7893 // Check for overflow 7894 char digit = c - '0'; 7895 if (number > (UINT_MAX - digit) / 10) 7896 return 0; // Overflow detected 7897 7898 number = number * 10 + digit; 7899 7900 p++; 7901 digits++; 7902 } 7903 7904 if (digits == 0) 7905 return 0; 7906 7907 *num = number; 7908 return 1; 7909 } 7910 7911 int ndb_client_event_from_json(const char *json, int len, struct ndb_fce *fce, 7912 unsigned char *buf, int bufsize, struct ndb_id_cb *cb) 7913 { 7914 jsmntok_t *tok = NULL; 7915 int tok_len, res; 7916 struct ndb_json_parser parser; 7917 struct ndb_event *ev = &fce->event; 7918 7919 ndb_json_parser_init(&parser, json, len, buf, bufsize); 7920 7921 if ((res = ndb_json_parser_parse(&parser, cb)) < 0) 7922 return res; 7923 7924 if (parser.toks[0].type == JSMN_OBJECT) { 7925 ndb_debug("got raw json in client_event_from_json\n"); 7926 fce->evtype = NDB_FCE_EVENT; 7927 return ndb_parse_json_note(&parser, &ev->note); 7928 } 7929 7930 if (parser.num_tokens <= 3 || parser.toks[0].type != JSMN_ARRAY) 7931 return 0; 7932 7933 parser.i = 1; 7934 tok = &parser.toks[parser.i++]; 7935 tok_len = toksize(tok); 7936 if (tok->type != JSMN_STRING) 7937 return 0; 7938 7939 if (tok_len == 5 && !memcmp("EVENT", json + tok->start, 5)) { 7940 fce->evtype = NDB_FCE_EVENT; 7941 return ndb_parse_json_note(&parser, &ev->note); 7942 } 7943 7944 return 0; 7945 } 7946 7947 7948 int ndb_ws_event_from_json(const char *json, int len, struct ndb_tce *tce, 7949 unsigned char *buf, int bufsize, 7950 struct ndb_id_cb *cb) 7951 { 7952 jsmntok_t *tok = NULL; 7953 int tok_len, res; 7954 struct ndb_json_parser parser; 7955 struct ndb_event *ev = &tce->event; 7956 7957 tce->subid_len = 0; 7958 tce->subid = ""; 7959 7960 ndb_json_parser_init(&parser, json, len, buf, bufsize); 7961 7962 if ((res = ndb_json_parser_parse(&parser, cb)) < 0) 7963 return res; 7964 7965 if (parser.toks[0].type == JSMN_OBJECT) { 7966 ndb_debug("got raw json in ws_event_from_json\n"); 7967 tce->evtype = NDB_TCE_EVENT; 7968 return ndb_parse_json_note(&parser, &ev->note); 7969 } 7970 7971 if (parser.num_tokens < 3 || parser.toks[0].type != JSMN_ARRAY) 7972 return 0; 7973 7974 parser.i = 1; 7975 tok = &parser.toks[parser.i++]; 7976 tok_len = toksize(tok); 7977 if (tok->type != JSMN_STRING) 7978 return 0; 7979 7980 if (tok_len == 5 && !memcmp("EVENT", json + tok->start, 5)) { 7981 tce->evtype = NDB_TCE_EVENT; 7982 7983 tok = &parser.toks[parser.i++]; 7984 if (tok->type != JSMN_STRING) 7985 return 0; 7986 7987 tce->subid = json + tok->start; 7988 tce->subid_len = toksize(tok); 7989 7990 return ndb_parse_json_note(&parser, &ev->note); 7991 } else if (tok_len == 4 && !memcmp("EOSE", json + tok->start, 4)) { 7992 tce->evtype = NDB_TCE_EOSE; 7993 7994 tok = &parser.toks[parser.i++]; 7995 if (tok->type != JSMN_STRING) 7996 return 0; 7997 7998 tce->subid = json + tok->start; 7999 tce->subid_len = toksize(tok); 8000 return 1; 8001 } else if (tok_len == 2 && !memcmp("OK", json + tok->start, 2)) { 8002 if (parser.num_tokens != 5) 8003 return 0; 8004 8005 struct ndb_command_result *cr = &tce->command_result; 8006 8007 tce->evtype = NDB_TCE_OK; 8008 8009 tok = &parser.toks[parser.i++]; 8010 if (tok->type != JSMN_STRING) 8011 return 0; 8012 8013 tce->subid = json + tok->start; 8014 tce->subid_len = toksize(tok); 8015 8016 tok = &parser.toks[parser.i++]; 8017 if (tok->type != JSMN_PRIMITIVE || toksize(tok) == 0) 8018 return 0; 8019 8020 cr->ok = (json + tok->start)[0] == 't'; 8021 8022 tok = &parser.toks[parser.i++]; 8023 if (tok->type != JSMN_STRING) 8024 return 0; 8025 8026 tce->command_result.msg = json + tok->start; 8027 tce->command_result.msglen = toksize(tok); 8028 8029 return 1; 8030 } else if (tok_len == 4 && !memcmp("AUTH", json + tok->start, 4)) { 8031 tce->evtype = NDB_TCE_AUTH; 8032 8033 tok = &parser.toks[parser.i++]; 8034 if (tok->type != JSMN_STRING) 8035 return 0; 8036 8037 tce->subid = json + tok->start; 8038 tce->subid_len = toksize(tok); 8039 8040 return 1; 8041 } 8042 8043 return 0; 8044 } 8045 8046 static enum ndb_filter_fieldtype 8047 ndb_filter_parse_field(const char *tok, int len, char *tagchar) 8048 { 8049 *tagchar = 0; 8050 8051 if (len == 0) 8052 return 0; 8053 8054 if (len == 7 && !strncmp(tok, "authors", 7)) { 8055 return NDB_FILTER_AUTHORS; 8056 } else if (len == 3 && !strncmp(tok, "ids", 3)) { 8057 return NDB_FILTER_IDS; 8058 } else if (len == 5 && !strncmp(tok, "kinds", 5)) { 8059 return NDB_FILTER_KINDS; 8060 } else if (len == 2 && tok[0] == '#') { 8061 *tagchar = tok[1]; 8062 return NDB_FILTER_TAGS; 8063 } else if (len == 5 && !strncmp(tok, "since", 5)) { 8064 return NDB_FILTER_SINCE; 8065 } else if (len == 5 && !strncmp(tok, "until", 5)) { 8066 return NDB_FILTER_UNTIL; 8067 } else if (len == 5 && !strncmp(tok, "limit", 5)) { 8068 return NDB_FILTER_LIMIT; 8069 } else if (len == 6 && !strncmp(tok, "search", 6)) { 8070 return NDB_FILTER_SEARCH; 8071 } 8072 8073 return 0; 8074 } 8075 8076 static int ndb_filter_parse_json_ids(struct ndb_json_parser *parser, 8077 struct ndb_filter *filter) 8078 { 8079 jsmntok_t *tok; 8080 const char *start; 8081 unsigned char hexbuf[32]; 8082 int tok_len, i, size; 8083 8084 tok = &parser->toks[parser->i++]; 8085 8086 if (tok->type != JSMN_ARRAY) { 8087 ndb_debug("parse_json_ids: not an array\n"); 8088 return 0; 8089 } 8090 8091 size = tok->size; 8092 8093 for (i = 0; i < size; parser->i++, i++) { 8094 tok = &parser->toks[parser->i]; 8095 start = parser->json + tok->start; 8096 tok_len = toksize(tok); 8097 8098 if (tok->type != JSMN_STRING) { 8099 ndb_debug("parse_json_ids: not a string '%d'\n", tok->type); 8100 return 0; 8101 } 8102 8103 if (tok_len != 64) { 8104 ndb_debug("parse_json_ids: not len 64: '%.*s'\n", tok_len, start); 8105 return 0; 8106 } 8107 8108 // id 8109 if (!hex_decode(start, tok_len, hexbuf, sizeof(hexbuf))) { 8110 ndb_debug("parse_json_ids: hex decode failed\n"); 8111 return 0; 8112 } 8113 8114 ndb_debug("adding id elem\n"); 8115 if (!ndb_filter_add_id_element(filter, hexbuf)) { 8116 ndb_debug("parse_json_ids: failed to add id element\n"); 8117 return 0; 8118 } 8119 } 8120 8121 parser->i--; 8122 return 1; 8123 } 8124 8125 static int ndb_filter_parse_json_elems(struct ndb_json_parser *parser, 8126 struct ndb_filter *filter) 8127 { 8128 jsmntok_t *tok; 8129 const char *start; 8130 int tok_len; 8131 unsigned char hexbuf[32]; 8132 enum ndb_generic_element_type typ; 8133 tok = NULL; 8134 int i, size; 8135 8136 tok = &parser->toks[parser->i++]; 8137 8138 if (tok->type != JSMN_ARRAY) 8139 return 0; 8140 8141 size = tok->size; 8142 8143 for (i = 0; i < size; i++, parser->i++) { 8144 tok = &parser->toks[parser->i]; 8145 start = parser->json + tok->start; 8146 tok_len = toksize(tok); 8147 8148 if (tok->type != JSMN_STRING) 8149 return 0; 8150 8151 if (i == 0) { 8152 if (tok_len == 64 && hex_decode(start, 64, hexbuf, sizeof(hexbuf))) { 8153 typ = NDB_ELEMENT_ID; 8154 if (!ndb_filter_add_id_element(filter, hexbuf)) { 8155 ndb_debug("failed to push id elem\n"); 8156 return 0; 8157 } 8158 } else { 8159 typ = NDB_ELEMENT_STRING; 8160 if (!ndb_filter_add_str_element_len(filter, start, tok_len)) 8161 return 0; 8162 } 8163 } else if (typ == NDB_ELEMENT_ID) { 8164 if (!hex_decode(start, 64, hexbuf, sizeof(hexbuf))) 8165 return 0; 8166 if (!ndb_filter_add_id_element(filter, hexbuf)) 8167 return 0; 8168 } else if (typ == NDB_ELEMENT_STRING) { 8169 if (!ndb_filter_add_str_element_len(filter, start, tok_len)) 8170 return 0; 8171 } else { 8172 // ??? 8173 return 0; 8174 } 8175 } 8176 8177 parser->i--; 8178 return 1; 8179 } 8180 8181 static int ndb_filter_parse_json_str(struct ndb_json_parser *parser, 8182 struct ndb_filter *filter) 8183 { 8184 jsmntok_t *tok; 8185 const char *start; 8186 int tok_len; 8187 8188 tok = &parser->toks[parser->i]; 8189 start = parser->json + tok->start; 8190 tok_len = toksize(tok); 8191 8192 if (tok->type != JSMN_STRING) 8193 return 0; 8194 8195 if (!ndb_filter_add_str_element_len(filter, start, tok_len)) 8196 return 0; 8197 8198 ndb_debug("added str elem '%.*s'\n", tok_len, start); 8199 8200 return 1; 8201 } 8202 8203 static int ndb_filter_parse_json_int(struct ndb_json_parser *parser, 8204 struct ndb_filter *filter) 8205 { 8206 jsmntok_t *tok; 8207 const char *start; 8208 int tok_len; 8209 unsigned int value; 8210 8211 tok = &parser->toks[parser->i]; 8212 start = parser->json + tok->start; 8213 tok_len = toksize(tok); 8214 8215 if (tok->type != JSMN_PRIMITIVE) 8216 return 0; 8217 8218 if (!parse_unsigned_int(start, tok_len, &value)) 8219 return 0; 8220 8221 if (!ndb_filter_add_int_element(filter, (uint64_t)value)) 8222 return 0; 8223 8224 ndb_debug("added int elem %d\n", value); 8225 8226 return 1; 8227 } 8228 8229 8230 static int ndb_filter_parse_json_ints(struct ndb_json_parser *parser, 8231 struct ndb_filter *filter) 8232 { 8233 jsmntok_t *tok; 8234 int size, i; 8235 8236 tok = &parser->toks[parser->i++]; 8237 8238 if (tok->type != JSMN_ARRAY) 8239 return 0; 8240 8241 size = tok->size; 8242 8243 for (i = 0; i < size; parser->i++, i++) { 8244 if (!ndb_filter_parse_json_int(parser, filter)) 8245 return 0; 8246 } 8247 8248 parser->i--; 8249 return 1; 8250 } 8251 8252 8253 static int ndb_filter_parse_json(struct ndb_json_parser *parser, 8254 struct ndb_filter *filter) 8255 { 8256 jsmntok_t *tok = NULL; 8257 const char *json = parser->json; 8258 const char *start; 8259 char tag; 8260 int tok_len; 8261 enum ndb_filter_fieldtype field; 8262 8263 if (parser->toks[parser->i++].type != JSMN_OBJECT) 8264 return 0; 8265 8266 for (; parser->i < parser->num_tokens; parser->i++) { 8267 tok = &parser->toks[parser->i++]; 8268 start = json + tok->start; 8269 tok_len = toksize(tok); 8270 8271 if (!(field = ndb_filter_parse_field(start, tok_len, &tag))) { 8272 ndb_debug("failed field '%.*s'\n", tok_len, start); 8273 continue; 8274 } 8275 8276 if (tag) { 8277 ndb_debug("starting tag field '%c'\n", tag); 8278 if (!ndb_filter_start_tag_field(filter, tag)) { 8279 ndb_debug("failed to start tag field '%c'\n", tag); 8280 return 0; 8281 } 8282 } else if (!ndb_filter_start_field(filter, field)) { 8283 ndb_debug("field already started\n"); 8284 return 0; 8285 } 8286 8287 // we parsed a top-level field 8288 switch(field) { 8289 case NDB_FILTER_CUSTOM: 8290 // can't really parse these yet 8291 break; 8292 case NDB_FILTER_AUTHORS: 8293 case NDB_FILTER_IDS: 8294 if (!ndb_filter_parse_json_ids(parser, filter)) { 8295 ndb_debug("failed to parse filter ids/authors\n"); 8296 return 0; 8297 } 8298 break; 8299 case NDB_FILTER_SEARCH: 8300 if (!ndb_filter_parse_json_str(parser, filter)) { 8301 ndb_debug("failed to parse filter search str\n"); 8302 return 0; 8303 } 8304 break; 8305 case NDB_FILTER_SINCE: 8306 case NDB_FILTER_UNTIL: 8307 case NDB_FILTER_LIMIT: 8308 if (!ndb_filter_parse_json_int(parser, filter)) { 8309 ndb_debug("failed to parse filter since/until/limit\n"); 8310 return 0; 8311 } 8312 break; 8313 case NDB_FILTER_KINDS: 8314 if (!ndb_filter_parse_json_ints(parser, filter)) { 8315 ndb_debug("failed to parse filter kinds\n"); 8316 return 0; 8317 } 8318 break; 8319 case NDB_FILTER_RELAYS: 8320 case NDB_FILTER_TAGS: 8321 if (!ndb_filter_parse_json_elems(parser, filter)) { 8322 ndb_debug("failed to parse filter tags\n"); 8323 return 0; 8324 } 8325 break; 8326 } 8327 8328 ndb_filter_end_field(filter); 8329 } 8330 8331 return ndb_filter_end(filter); 8332 } 8333 8334 int ndb_parse_json_note(struct ndb_json_parser *parser, struct ndb_note **note) 8335 { 8336 jsmntok_t *tok = NULL; 8337 unsigned char hexbuf[64]; 8338 const char *json = parser->json; 8339 const char *start; 8340 int i, tok_len, parsed; 8341 8342 parsed = 0; 8343 8344 if (parser->toks[parser->i].type != JSMN_OBJECT) 8345 return 0; 8346 8347 // TODO: build id buffer and verify at end 8348 8349 for (i = parser->i + 1; i < parser->num_tokens; i++) { 8350 tok = &parser->toks[i]; 8351 start = json + tok->start; 8352 tok_len = toksize(tok); 8353 8354 //printf("toplevel %.*s %d\n", tok_len, json + tok->start, tok->type); 8355 if (tok_len == 0 || i + 1 >= parser->num_tokens) 8356 continue; 8357 8358 if (start[0] == 'p' && jsoneq(json, tok, tok_len, "pubkey")) { 8359 // pubkey 8360 tok = &parser->toks[i+1]; 8361 hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf)); 8362 parsed |= NDB_PARSED_PUBKEY; 8363 ndb_builder_set_pubkey(&parser->builder, hexbuf); 8364 } else if (tok_len == 2 && start[0] == 'i' && start[1] == 'd') { 8365 // id 8366 tok = &parser->toks[i+1]; 8367 hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf)); 8368 parsed |= NDB_PARSED_ID; 8369 ndb_builder_set_id(&parser->builder, hexbuf); 8370 } else if (tok_len == 3 && start[0] == 's' && start[1] == 'i' && start[2] == 'g') { 8371 // sig 8372 tok = &parser->toks[i+1]; 8373 hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf)); 8374 parsed |= NDB_PARSED_SIG; 8375 ndb_builder_set_sig(&parser->builder, hexbuf); 8376 } else if (start[0] == 'k' && jsoneq(json, tok, tok_len, "kind")) { 8377 // kind 8378 tok = &parser->toks[i+1]; 8379 start = json + tok->start; 8380 if (tok->type != JSMN_PRIMITIVE || tok_len <= 0) 8381 return 0; 8382 if (!parse_unsigned_int(start, toksize(tok), 8383 &parser->builder.note->kind)) 8384 return 0; 8385 parsed |= NDB_PARSED_KIND; 8386 } else if (start[0] == 'c') { 8387 if (jsoneq(json, tok, tok_len, "created_at")) { 8388 // created_at 8389 tok = &parser->toks[i+1]; 8390 start = json + tok->start; 8391 if (tok->type != JSMN_PRIMITIVE || tok_len <= 0) 8392 return 0; 8393 // TODO: update to int64 in 2106 ... xD 8394 unsigned int bigi; 8395 if (!parse_unsigned_int(start, toksize(tok), &bigi)) 8396 return 0; 8397 parser->builder.note->created_at = bigi; 8398 parsed |= NDB_PARSED_CREATED_AT; 8399 } else if (jsoneq(json, tok, tok_len, "content")) { 8400 // content 8401 tok = &parser->toks[i+1]; 8402 union ndb_packed_str pstr; 8403 tok_len = toksize(tok); 8404 int written, pack_ids = 0; 8405 if (!ndb_builder_make_json_str(&parser->builder, 8406 json + tok->start, 8407 tok_len, &pstr, 8408 &written, pack_ids)) { 8409 ndb_debug("ndb_builder_make_json_str failed\n"); 8410 return 0; 8411 } 8412 parser->builder.note->content_length = written; 8413 parser->builder.note->content = pstr; 8414 parsed |= NDB_PARSED_CONTENT; 8415 } 8416 } else if (start[0] == 't' && jsoneq(json, tok, tok_len, "tags")) { 8417 tok = &parser->toks[i+1]; 8418 ndb_builder_process_json_tags(parser, tok); 8419 i += tok->size; 8420 parsed |= NDB_PARSED_TAGS; 8421 } 8422 } 8423 8424 //ndb_debug("parsed %d = %d, &->%d", parsed, NDB_PARSED_ALL, parsed & NDB_PARSED_ALL); 8425 if (parsed != NDB_PARSED_ALL) 8426 return 0; 8427 8428 return ndb_builder_finalize(&parser->builder, note, NULL); 8429 } 8430 8431 int ndb_filter_from_json(const char *json, int len, struct ndb_filter *filter, 8432 unsigned char *buf, int bufsize) 8433 { 8434 struct ndb_json_parser parser; 8435 int res; 8436 8437 if (filter->finalized) 8438 return 0; 8439 8440 ndb_json_parser_init(&parser, json, len, buf, bufsize); 8441 if ((res = ndb_json_parser_parse(&parser, NULL)) < 0) 8442 return res; 8443 8444 if (parser.num_tokens < 1) 8445 return 0; 8446 8447 return ndb_filter_parse_json(&parser, filter); 8448 } 8449 8450 int ndb_note_from_json(const char *json, int len, struct ndb_note **note, 8451 unsigned char *buf, int bufsize) 8452 { 8453 struct ndb_json_parser parser; 8454 int res; 8455 8456 ndb_json_parser_init(&parser, json, len, buf, bufsize); 8457 if ((res = ndb_json_parser_parse(&parser, NULL)) < 0) 8458 return res; 8459 8460 if (parser.num_tokens < 1) 8461 return 0; 8462 8463 return ndb_parse_json_note(&parser, note); 8464 } 8465 8466 void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey) 8467 { 8468 memcpy(builder->note->pubkey, pubkey, 32); 8469 } 8470 8471 void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id) 8472 { 8473 memcpy(builder->note->id, id, 32); 8474 } 8475 8476 void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig) 8477 { 8478 memcpy(builder->note->sig, sig, 64); 8479 } 8480 8481 void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind) 8482 { 8483 builder->note->kind = kind; 8484 } 8485 8486 void ndb_builder_set_created_at(struct ndb_builder *builder, uint64_t created_at) 8487 { 8488 builder->note->created_at = created_at; 8489 } 8490 8491 int ndb_builder_new_tag(struct ndb_builder *builder) 8492 { 8493 builder->note->tags.count++; 8494 struct ndb_tag tag = {0}; 8495 builder->current_tag = (struct ndb_tag *)builder->note_cur.p; 8496 return cursor_push_tag(&builder->note_cur, &tag); 8497 } 8498 8499 void ndb_stat_counts_init(struct ndb_stat_counts *counts) 8500 { 8501 counts->count = 0; 8502 counts->key_size = 0; 8503 counts->value_size = 0; 8504 } 8505 8506 static void ndb_stat_init(struct ndb_stat *stat) 8507 { 8508 // init stats 8509 int i; 8510 8511 for (i = 0; i < NDB_CKIND_COUNT; i++) { 8512 ndb_stat_counts_init(&stat->common_kinds[i]); 8513 } 8514 8515 for (i = 0; i < NDB_DBS; i++) { 8516 ndb_stat_counts_init(&stat->dbs[i]); 8517 } 8518 8519 ndb_stat_counts_init(&stat->other_kinds); 8520 } 8521 8522 int ndb_stat(struct ndb *ndb, struct ndb_stat *stat) 8523 { 8524 int rc; 8525 MDB_cursor *cur; 8526 MDB_val k, v; 8527 MDB_dbi db; 8528 struct ndb_txn txn; 8529 struct ndb_note *note; 8530 int i; 8531 enum ndb_common_kind common_kind; 8532 8533 // initialize to 0 8534 ndb_stat_init(stat); 8535 8536 if (!ndb_begin_query(ndb, &txn)) { 8537 fprintf(stderr, "ndb_stat failed at ndb_begin_query\n"); 8538 return 0; 8539 } 8540 8541 // stat each dbi in the database 8542 for (i = 0; i < NDB_DBS; i++) 8543 { 8544 db = ndb->lmdb.dbs[i]; 8545 8546 if ((rc = mdb_cursor_open(txn.mdb_txn, db, &cur))) { 8547 fprintf(stderr, "ndb_stat: mdb_cursor_open failed, error '%s'\n", 8548 mdb_strerror(rc)); 8549 return 0; 8550 } 8551 8552 // loop over every entry and count kv sizes 8553 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8554 // we gather more detailed per-kind stats if we're in 8555 // the notes db 8556 if (i == NDB_DB_NOTE) { 8557 note = v.mv_data; 8558 common_kind = ndb_kind_to_common_kind(note->kind); 8559 8560 // uncommon kind? just count them in bulk 8561 if ((int)common_kind == -1) { 8562 stat->other_kinds.count++; 8563 stat->other_kinds.key_size += k.mv_size; 8564 stat->other_kinds.value_size += v.mv_size; 8565 } else { 8566 stat->common_kinds[common_kind].count++; 8567 stat->common_kinds[common_kind].key_size += k.mv_size; 8568 stat->common_kinds[common_kind].value_size += v.mv_size; 8569 } 8570 } 8571 8572 stat->dbs[i].count++; 8573 stat->dbs[i].key_size += k.mv_size; 8574 stat->dbs[i].value_size += v.mv_size; 8575 } 8576 8577 // close the cursor, they are per-dbi 8578 mdb_cursor_close(cur); 8579 } 8580 8581 ndb_end_query(&txn); 8582 8583 return 1; 8584 } 8585 8586 /// Push an element to the current tag 8587 /// 8588 /// Basic idea is to call ndb_builder_new_tag 8589 int ndb_builder_push_tag_str(struct ndb_builder *builder, 8590 const char *str, int len) 8591 { 8592 union ndb_packed_str pstr; 8593 int pack_ids = 1; 8594 if (!ndb_builder_make_str(builder, str, len, &pstr, pack_ids)) 8595 return 0; 8596 return ndb_builder_finalize_tag(builder, pstr); 8597 } 8598 8599 /// Push an id element to the current tag. Needs to be 32 bytes 8600 int ndb_builder_push_tag_id(struct ndb_builder *builder, 8601 unsigned char *id) 8602 { 8603 union ndb_packed_str pstr; 8604 if (!ndb_builder_push_packed_id(builder, id, &pstr)) 8605 return 0; 8606 return ndb_builder_finalize_tag(builder, pstr); 8607 } 8608 8609 // 8610 // CONFIG 8611 // 8612 void ndb_default_config(struct ndb_config *config) 8613 { 8614 int cores = get_cpu_cores(); 8615 config->mapsize = 1024UL * 1024UL * 1024UL * 32UL; // 32 GiB 8616 config->ingester_threads = cores == -1 ? 4 : cores; 8617 config->flags = 0; 8618 config->ingest_filter = NULL; 8619 config->filter_context = NULL; 8620 config->sub_cb_ctx = NULL; 8621 config->sub_cb = NULL; 8622 config->writer_scratch_buffer_size = DEFAULT_WRITER_SCRATCH_SIZE; 8623 } 8624 8625 void ndb_config_set_subscription_callback(struct ndb_config *config, ndb_sub_fn fn, void *context) 8626 { 8627 config->sub_cb_ctx = context; 8628 config->sub_cb = fn; 8629 } 8630 8631 void ndb_config_set_writer_scratch_buffer_size(struct ndb_config *config, int scratch_size) 8632 { 8633 config->writer_scratch_buffer_size = scratch_size; 8634 } 8635 8636 void ndb_config_set_ingest_threads(struct ndb_config *config, int threads) 8637 { 8638 config->ingester_threads = threads; 8639 } 8640 8641 void ndb_config_set_flags(struct ndb_config *config, int flags) 8642 { 8643 config->flags = flags; 8644 } 8645 8646 void ndb_config_set_mapsize(struct ndb_config *config, size_t mapsize) 8647 { 8648 config->mapsize = mapsize; 8649 } 8650 8651 void ndb_config_set_ingest_filter(struct ndb_config *config, 8652 ndb_ingest_filter_fn fn, void *filter_ctx) 8653 { 8654 config->ingest_filter = fn; 8655 config->filter_context = filter_ctx; 8656 } 8657 8658 int ndb_print_note_metadata(struct ndb_txn *txn) 8659 { 8660 MDB_cursor *cur; 8661 MDB_val k, v; 8662 int i; 8663 8664 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &cur)) 8665 return 0; 8666 8667 i = 1; 8668 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8669 print_hex(k.mv_data, 32); 8670 printf("\t"); 8671 print_note_meta((struct ndb_note_meta*)v.mv_data); 8672 i++; 8673 } 8674 8675 mdb_cursor_close(cur); 8676 8677 return i; 8678 } 8679 8680 8681 int ndb_print_author_kind_index(struct ndb_txn *txn) 8682 { 8683 MDB_cursor *cur; 8684 struct ndb_id_u64_ts *key; 8685 MDB_val k, v; 8686 int i; 8687 8688 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_PUBKEY_KIND], &cur)) 8689 return 0; 8690 8691 i = 1; 8692 printf("author\tkind\tcreated_at\tnote_id\n"); 8693 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8694 key = (struct ndb_id_u64_ts *)k.mv_data; 8695 print_hex(key->id, 32); 8696 printf("\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\n", 8697 key->u64, key->timestamp, *(uint64_t*)v.mv_data); 8698 i++; 8699 } 8700 8701 mdb_cursor_close(cur); 8702 8703 return i; 8704 } 8705 8706 int ndb_print_relay_kind_index(struct ndb_txn *txn) 8707 { 8708 MDB_cursor *cur; 8709 unsigned char *d; 8710 MDB_val k, v; 8711 int i; 8712 8713 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAY_KIND], &cur)) 8714 return 0; 8715 8716 i = 1; 8717 printf("relay\tkind\tcreated_at\tnote_id\n"); 8718 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8719 d = (unsigned char *)k.mv_data; 8720 printf("%s\t", (const char *)(d + 25)); 8721 printf("%" PRIu64 "\t", *(uint64_t*)(d + 8)); 8722 printf("%" PRIu64 "\t", *(uint64_t*)(d + 16)); 8723 printf("%" PRIu64 "\n", *(uint64_t*)(d + 0)); 8724 i++; 8725 } 8726 8727 mdb_cursor_close(cur); 8728 8729 return i; 8730 } 8731 8732 int ndb_print_tag_index(struct ndb_txn *txn) 8733 { 8734 MDB_cursor *cur; 8735 MDB_val k, v; 8736 int i; 8737 8738 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_TAGS], &cur)) 8739 return 0; 8740 8741 i = 1; 8742 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8743 printf("%d ", i); 8744 print_tag_kv(txn, &k, &v); 8745 i++; 8746 } 8747 8748 mdb_cursor_close(cur); 8749 8750 return 1; 8751 } 8752 8753 int ndb_print_kind_keys(struct ndb_txn *txn) 8754 { 8755 MDB_cursor *cur; 8756 MDB_val k, v; 8757 int i; 8758 struct ndb_u64_ts *tsid; 8759 8760 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_KIND], &cur)) 8761 return 0; 8762 8763 i = 1; 8764 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8765 tsid = k.mv_data; 8766 printf("%d note_kind %" PRIu64 " %" PRIu64 "\n", 8767 i, tsid->u64, tsid->timestamp); 8768 8769 i++; 8770 } 8771 8772 mdb_cursor_close(cur); 8773 8774 return 1; 8775 } 8776 8777 // used by ndb.c 8778 int ndb_print_search_keys(struct ndb_txn *txn) 8779 { 8780 MDB_cursor *cur; 8781 MDB_val k, v; 8782 int i; 8783 struct ndb_text_search_key search_key; 8784 8785 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_TEXT], &cur)) 8786 return 0; 8787 8788 i = 1; 8789 while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { 8790 if (!ndb_unpack_text_search_key(k.mv_data, k.mv_size, &search_key)) { 8791 fprintf(stderr, "error decoding key %d\n", i); 8792 continue; 8793 } 8794 8795 ndb_print_text_search_key(k.mv_size, &search_key); 8796 printf("\n"); 8797 8798 i++; 8799 } 8800 8801 mdb_cursor_close(cur); 8802 8803 return 1; 8804 } 8805 8806 struct ndb_tags *ndb_note_tags(struct ndb_note *note) 8807 { 8808 return ¬e->tags; 8809 } 8810 8811 struct ndb_str ndb_note_str(struct ndb_note *note, union ndb_packed_str *pstr) 8812 { 8813 struct ndb_str str; 8814 str.flag = pstr->packed.flag; 8815 8816 if (str.flag == NDB_PACKED_STR) { 8817 str.str = pstr->packed.str; 8818 return str; 8819 } 8820 8821 str.str = ((const char *)note) + note->strings + (pstr->offset & 0xFFFFFF); 8822 return str; 8823 } 8824 8825 struct ndb_str ndb_tag_str(struct ndb_note *note, struct ndb_tag *tag, int ind) 8826 { 8827 return ndb_note_str(note, &tag->strs[ind]); 8828 } 8829 8830 int ndb_str_len(struct ndb_str *str) 8831 { 8832 if (str->flag == NDB_PACKED_ID) 8833 return 32; 8834 return strlen(str->str); 8835 } 8836 8837 struct ndb_str ndb_iter_tag_str(struct ndb_iterator *iter, int ind) 8838 { 8839 return ndb_tag_str(iter->note, iter->tag, ind); 8840 } 8841 8842 unsigned char * ndb_note_id(struct ndb_note *note) 8843 { 8844 return note->id; 8845 } 8846 8847 unsigned char * ndb_note_pubkey(struct ndb_note *note) 8848 { 8849 return note->pubkey; 8850 } 8851 8852 unsigned char * ndb_note_sig(struct ndb_note *note) 8853 { 8854 return note->sig; 8855 } 8856 8857 uint32_t ndb_note_created_at(struct ndb_note *note) 8858 { 8859 return note->created_at; 8860 } 8861 8862 uint32_t ndb_note_kind(struct ndb_note *note) 8863 { 8864 return note->kind; 8865 } 8866 8867 void _ndb_note_set_kind(struct ndb_note *note, uint32_t kind) 8868 { 8869 note->kind = kind; 8870 } 8871 8872 const char *ndb_note_content(struct ndb_note *note) 8873 { 8874 return ndb_note_str(note, ¬e->content).str; 8875 } 8876 8877 uint32_t ndb_note_content_length(struct ndb_note *note) 8878 { 8879 return note->content_length; 8880 } 8881 8882 struct ndb_note * ndb_note_from_bytes(unsigned char *bytes) 8883 { 8884 struct ndb_note *note = (struct ndb_note *)bytes; 8885 if (note->version != 1) 8886 return 0; 8887 return note; 8888 } 8889 8890 int ndb_note_relay_iterate_start(struct ndb_txn *txn, 8891 struct ndb_note_relay_iterator *iter, 8892 uint64_t note_key) 8893 { 8894 if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_NOTE_RELAYS], 8895 (MDB_cursor**)&iter->mdb_cur)) { 8896 return 0; 8897 } 8898 8899 iter->txn = txn; 8900 iter->cursor_op = MDB_SET_KEY; 8901 iter->note_key = note_key; 8902 8903 return 1; 8904 } 8905 8906 const char *ndb_note_relay_iterate_next(struct ndb_note_relay_iterator *iter) 8907 { 8908 int rc; 8909 MDB_val k, v; 8910 8911 if (iter->mdb_cur == NULL) 8912 return NULL; 8913 8914 k.mv_data = &iter->note_key; 8915 k.mv_size = sizeof(iter->note_key); 8916 8917 if ((rc = mdb_cursor_get((MDB_cursor *)iter->mdb_cur, &k, &v, 8918 (MDB_cursor_op)iter->cursor_op))) 8919 { 8920 //fprintf(stderr, "autoclosing %d '%s'\n", iter->cursor_op, mdb_strerror(rc)); 8921 // autoclose 8922 ndb_note_relay_iterate_close(iter); 8923 return NULL; 8924 } 8925 8926 iter->cursor_op = MDB_NEXT_DUP; 8927 8928 return (const char*)v.mv_data; 8929 } 8930 8931 void ndb_note_relay_iterate_close(struct ndb_note_relay_iterator *iter) 8932 { 8933 if (!iter || iter->mdb_cur == NULL) 8934 return; 8935 8936 mdb_cursor_close((MDB_cursor*)iter->mdb_cur); 8937 8938 iter->mdb_cur = NULL; 8939 } 8940 8941 void ndb_tags_iterate_start(struct ndb_note *note, struct ndb_iterator *iter) 8942 { 8943 iter->note = note; 8944 iter->tag = NULL; 8945 iter->index = -1; 8946 } 8947 8948 // Helper function to get a pointer to the nth tag 8949 static struct ndb_tag *ndb_tags_tag(struct ndb_tags *tags, size_t index) { 8950 return (struct ndb_tag *)((uint8_t *)tags + sizeof(struct ndb_tags) + index * sizeof(struct ndb_tag)); 8951 } 8952 8953 int ndb_tags_iterate_next(struct ndb_iterator *iter) 8954 { 8955 struct ndb_tags *tags; 8956 8957 if (iter->tag == NULL || iter->index == -1) { 8958 iter->tag = ndb_tags_tag(&iter->note->tags, 0); 8959 iter->index = 0; 8960 return iter->note->tags.count != 0; 8961 } 8962 8963 tags = &iter->note->tags; 8964 8965 if (++iter->index < tags->count) { 8966 uint32_t tag_data_size = iter->tag->count * sizeof(iter->tag->strs[0]); 8967 iter->tag = (struct ndb_tag *)(iter->tag->strs[0].bytes + tag_data_size); 8968 return 1; 8969 } 8970 8971 return 0; 8972 } 8973 8974 uint16_t ndb_tags_count(struct ndb_tags *tags) 8975 { 8976 return tags->count; 8977 } 8978 8979 uint16_t ndb_tag_count(struct ndb_tag *tags) 8980 { 8981 return tags->count; 8982 } 8983 8984 enum ndb_common_kind ndb_kind_to_common_kind(int kind) 8985 { 8986 switch (kind) 8987 { 8988 case 0: return NDB_CKIND_PROFILE; 8989 case 1: return NDB_CKIND_TEXT; 8990 case 3: return NDB_CKIND_CONTACTS; 8991 case 4: return NDB_CKIND_DM; 8992 case 5: return NDB_CKIND_DELETE; 8993 case 6: return NDB_CKIND_REPOST; 8994 case 7: return NDB_CKIND_REACTION; 8995 case 9735: return NDB_CKIND_ZAP; 8996 case 9734: return NDB_CKIND_ZAP_REQUEST; 8997 case 23194: return NDB_CKIND_NWC_REQUEST; 8998 case 23195: return NDB_CKIND_NWC_RESPONSE; 8999 case 27235: return NDB_CKIND_HTTP_AUTH; 9000 case 30000: return NDB_CKIND_LIST; 9001 case 30023: return NDB_CKIND_LONGFORM; 9002 case 30315: return NDB_CKIND_STATUS; 9003 } 9004 9005 return -1; 9006 } 9007 9008 const char *ndb_kind_name(enum ndb_common_kind ck) 9009 { 9010 switch (ck) { 9011 case NDB_CKIND_PROFILE: return "profile"; 9012 case NDB_CKIND_TEXT: return "text"; 9013 case NDB_CKIND_CONTACTS: return "contacts"; 9014 case NDB_CKIND_DM: return "dm"; 9015 case NDB_CKIND_DELETE: return "delete"; 9016 case NDB_CKIND_REPOST: return "repost"; 9017 case NDB_CKIND_REACTION: return "reaction"; 9018 case NDB_CKIND_ZAP: return "zap"; 9019 case NDB_CKIND_ZAP_REQUEST: return "zap_request"; 9020 case NDB_CKIND_NWC_REQUEST: return "nwc_request"; 9021 case NDB_CKIND_NWC_RESPONSE: return "nwc_response"; 9022 case NDB_CKIND_HTTP_AUTH: return "http_auth"; 9023 case NDB_CKIND_LIST: return "list"; 9024 case NDB_CKIND_LONGFORM: return "longform"; 9025 case NDB_CKIND_STATUS: return "status"; 9026 case NDB_CKIND_COUNT: return "unknown"; 9027 } 9028 9029 return "unknown"; 9030 } 9031 9032 const char *ndb_db_name(enum ndb_dbs db) 9033 { 9034 switch (db) { 9035 case NDB_DB_NOTE: 9036 return "note"; 9037 case NDB_DB_META: 9038 return "note_metadata"; 9039 case NDB_DB_PROFILE: 9040 return "profile"; 9041 case NDB_DB_NOTE_ID: 9042 return "note_index"; 9043 case NDB_DB_PROFILE_PK: 9044 return "profile_pubkey_index"; 9045 case NDB_DB_NDB_META: 9046 return "nostrdb_metadata"; 9047 case NDB_DB_PROFILE_SEARCH: 9048 return "profile_search"; 9049 case NDB_DB_PROFILE_LAST_FETCH: 9050 return "profile_last_fetch"; 9051 case NDB_DB_NOTE_KIND: 9052 return "note_kind_index"; 9053 case NDB_DB_NOTE_TEXT: 9054 return "note_fulltext"; 9055 case NDB_DB_NOTE_BLOCKS: 9056 return "note_blocks"; 9057 case NDB_DB_NOTE_TAGS: 9058 return "note_tags"; 9059 case NDB_DB_NOTE_PUBKEY: 9060 return "note_pubkey_index"; 9061 case NDB_DB_NOTE_PUBKEY_KIND: 9062 return "note_pubkey_kind_index"; 9063 case NDB_DB_NOTE_RELAY_KIND: 9064 return "note_relay_kind_index"; 9065 case NDB_DB_NOTE_RELAYS: 9066 return "note_relays"; 9067 case NDB_DBS: 9068 return "count"; 9069 } 9070 9071 return "unknown"; 9072 } 9073 9074 static struct ndb_blocks *ndb_note_to_blocks(struct ndb_note *note) 9075 { 9076 const char *content; 9077 size_t content_len; 9078 struct ndb_blocks *blocks; 9079 unsigned char *buffer; 9080 9081 content = ndb_note_content(note); 9082 content_len = ndb_note_content_length(note); 9083 9084 // something weird is going on 9085 if (content_len >= INT32_MAX) 9086 return NULL; 9087 9088 buffer = malloc(2<<18); // Not carefully calculated, but ok because we will not need this once we switch to the local relay model 9089 if (!buffer) 9090 return NULL; 9091 9092 if (!ndb_parse_content(buffer, content_len, content, content_len, &blocks)) { 9093 free(buffer); 9094 return NULL; 9095 } 9096 9097 //blocks = realloc(blocks, ndb_blocks_total_size(blocks)); 9098 //if (blocks == NULL) 9099 //return NULL; 9100 9101 blocks->flags |= NDB_BLOCK_FLAG_OWNED; 9102 9103 return blocks; 9104 } 9105 9106 struct ndb_blocks *ndb_get_blocks_by_key(struct ndb *ndb, struct ndb_txn *txn, uint64_t note_key) 9107 { 9108 struct ndb_blocks *blocks, *blocks_to_writer; 9109 size_t blocks_size; 9110 struct ndb_note *note; 9111 size_t note_len; 9112 9113 if ((blocks = ndb_lookup_by_key(txn, note_key, NDB_DB_NOTE_BLOCKS, ¬e_len))) { 9114 return blocks; 9115 } 9116 9117 // If we don't have note blocks, let's lazily generate them. This is 9118 // migration-friendly instead of doing them all at once 9119 if (!(note = ndb_get_note_by_key(txn, note_key, ¬e_len))) { 9120 // no note found, can't return note blocks 9121 return NULL; 9122 } 9123 9124 if (!(blocks = ndb_note_to_blocks(note))) 9125 return NULL; 9126 9127 // send a copy to the writer 9128 blocks_size = ndb_blocks_total_size(blocks); 9129 blocks_to_writer = malloc(blocks_size); 9130 memcpy(blocks_to_writer, blocks, blocks_size); 9131 assert(blocks->flags & NDB_BLOCK_FLAG_OWNED); 9132 9133 // we generated new blocks, let's store them in the DB 9134 struct ndb_writer_blocks write_blocks = { 9135 .blocks = blocks_to_writer, 9136 .note_key = note_key 9137 }; 9138 9139 assert(write_blocks.blocks != blocks); 9140 9141 struct ndb_writer_msg msg = { .type = NDB_WRITER_BLOCKS }; 9142 msg.blocks = write_blocks; 9143 9144 ndb_writer_queue_msg(&ndb->writer, &msg); 9145 9146 return blocks; 9147 } 9148 9149 // please call ndb_monitor_lock before calling this 9150 static struct ndb_subscription * 9151 ndb_monitor_find_subscription(struct ndb_monitor *monitor, uint64_t subid, int *index) 9152 { 9153 struct ndb_subscription *sub, *tsub; 9154 int i; 9155 9156 for (i = 0, sub = NULL; i < monitor->num_subscriptions; i++) { 9157 tsub = &monitor->subscriptions[i]; 9158 if (tsub->subid == subid) { 9159 sub = tsub; 9160 if (index) 9161 *index = i; 9162 break; 9163 } 9164 } 9165 9166 return sub; 9167 } 9168 9169 int ndb_poll_for_notes(struct ndb *ndb, uint64_t subid, uint64_t *note_ids, 9170 int note_id_capacity) 9171 { 9172 struct ndb_subscription *sub; 9173 int res; 9174 9175 if (subid == 0) 9176 return 0; 9177 9178 ndb_monitor_lock(&ndb->monitor); 9179 9180 if (!(sub = ndb_monitor_find_subscription(&ndb->monitor, subid, NULL))) 9181 res = 0; 9182 else 9183 res = prot_queue_try_pop_all(&sub->inbox, note_ids, note_id_capacity); 9184 9185 ndb_monitor_unlock(&ndb->monitor); 9186 9187 return res; 9188 } 9189 9190 int ndb_wait_for_notes(struct ndb *ndb, uint64_t subid, uint64_t *note_ids, 9191 int note_id_capacity) 9192 { 9193 struct ndb_subscription *sub; 9194 struct prot_queue *queue_inbox; 9195 9196 // this is not a valid subscription id 9197 if (subid == 0) 9198 return 0; 9199 9200 ndb_monitor_lock(&ndb->monitor); 9201 9202 if (!(sub = ndb_monitor_find_subscription(&ndb->monitor, subid, NULL))) { 9203 ndb_monitor_unlock(&ndb->monitor); 9204 return 0; 9205 } 9206 9207 queue_inbox = &sub->inbox; 9208 9209 ndb_monitor_unlock(&ndb->monitor); 9210 9211 // there is technically a race condition if the thread yeilds at this 9212 // comment and a subscription is added/removed. A deadlock in the 9213 // writer queue would be much worse though. This function is dubious 9214 // anyways. 9215 9216 return prot_queue_pop_all(queue_inbox, note_ids, note_id_capacity); 9217 } 9218 9219 int ndb_unsubscribe(struct ndb *ndb, uint64_t subid) 9220 { 9221 struct ndb_subscription *sub; 9222 int index, res, elems_to_move; 9223 9224 ndb_monitor_lock(&ndb->monitor); 9225 9226 if (!(sub = ndb_monitor_find_subscription(&ndb->monitor, subid, &index))) { 9227 res = 0; 9228 goto done; 9229 } 9230 9231 ndb_subscription_destroy(sub); 9232 9233 elems_to_move = (--ndb->monitor.num_subscriptions) - index; 9234 9235 memmove(&ndb->monitor.subscriptions[index], 9236 &ndb->monitor.subscriptions[index+1], 9237 elems_to_move * sizeof(*sub)); 9238 9239 res = 1; 9240 9241 done: 9242 ndb_monitor_unlock(&ndb->monitor); 9243 9244 return res; 9245 } 9246 9247 int ndb_num_subscriptions(struct ndb *ndb) 9248 { 9249 return ndb->monitor.num_subscriptions; 9250 } 9251 9252 uint64_t ndb_subscribe(struct ndb *ndb, struct ndb_filter *filters, int num_filters) 9253 { 9254 static uint64_t subids = 0; 9255 struct ndb_subscription *sub; 9256 size_t buflen; 9257 uint64_t subid; 9258 char *buf; 9259 9260 ndb_monitor_lock(&ndb->monitor); 9261 9262 if (ndb->monitor.num_subscriptions + 1 >= MAX_SUBSCRIPTIONS) { 9263 fprintf(stderr, "too many subscriptions\n"); 9264 subid = 0; 9265 goto done; 9266 } 9267 9268 sub = &ndb->monitor.subscriptions[ndb->monitor.num_subscriptions]; 9269 subid = ++subids; 9270 sub->subid = subid; 9271 9272 ndb_filter_group_init(&sub->group); 9273 if (!ndb_filter_group_add_filters(&sub->group, filters, num_filters)) { 9274 subid = 0; 9275 goto done; 9276 } 9277 9278 // 500k ought to be enough for anyone 9279 buflen = sizeof(uint64_t) * DEFAULT_QUEUE_SIZE; 9280 buf = malloc(buflen); 9281 9282 if (!prot_queue_init(&sub->inbox, buf, buflen, sizeof(uint64_t))) { 9283 fprintf(stderr, "failed to push prot queue\n"); 9284 subid = 0; 9285 goto done; 9286 } 9287 9288 ndb->monitor.num_subscriptions++; 9289 done: 9290 ndb_monitor_unlock(&ndb->monitor); 9291 9292 return subid; 9293 }