note.rs (19658B)
1 use crate::{bindings, tags::Tags, transaction::Transaction, Error, NoteRelays}; 2 use std::{hash::Hash, os::raw::c_uchar}; 3 4 #[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)] 5 pub struct NoteKey(u64); 6 7 impl NoteKey { 8 pub fn as_u64(&self) -> u64 { 9 self.0 10 } 11 12 pub fn new(key: u64) -> Self { 13 NoteKey(key) 14 } 15 } 16 17 pub struct NoteBuildOptions<'a> { 18 /// Generate the created_at based on the current time, otherwise the id field will remain untouched 19 pub set_created_at: bool, 20 21 /// Sign with the secret key, otherwise sig field will remain untouched 22 pub sign_key: Option<&'a [u8; 32]>, 23 } 24 25 impl Default for NoteBuildOptions<'_> { 26 fn default() -> Self { 27 NoteBuildOptions { 28 set_created_at: true, 29 sign_key: None, 30 } 31 } 32 } 33 34 impl<'a> NoteBuildOptions<'a> { 35 pub fn created_at(mut self, set_created_at: bool) -> Self { 36 self.set_created_at = set_created_at; 37 self 38 } 39 40 pub fn sign(mut self, seckey: &'a [u8; 32]) -> NoteBuildOptions<'a> { 41 self.sign_key = Some(seckey); 42 self 43 } 44 } 45 46 #[derive(Debug)] 47 pub enum Note<'a> { 48 /// A note in-memory outside of nostrdb. This note is a pointer to a note in 49 /// memory and will be free'd when [Drop]ped. Method such as [Note::from_json] 50 /// will create owned notes in memory. 51 /// 52 /// [Drop]: std::ops::Drop 53 Owned { 54 ptr: *mut bindings::ndb_note, 55 size: usize, 56 }, 57 58 /// An note owned somewhere else. We don't know if its from the DB or not. This 59 /// is used for custom filter callbacks. 60 Unowned { ptr: &'a bindings::ndb_note }, 61 62 /// A note inside of nostrdb. Tied to the lifetime of a 63 /// [Transaction] to ensure no reading of data outside 64 /// of a transaction. 65 Transactional { 66 ptr: *mut bindings::ndb_note, 67 size: usize, 68 key: NoteKey, 69 transaction: &'a Transaction, 70 }, 71 } 72 73 impl Clone for Note<'_> { 74 fn clone(&self) -> Self { 75 // TODO (jb55): it is still probably better to just separate owned notes 76 // into NoteBuf... that way we know exactly what we are cloning 77 // and when. Owned notes are a bit more expensive to clone, so 78 // it would be better if API encoded that explicitly. 79 match self { 80 Note::Unowned { ptr } => Note::Unowned { ptr }, 81 82 Note::Owned { ptr, size } => { 83 // Allocate memory for the cloned note 84 let new_ptr = unsafe { libc::malloc(*size) as *mut bindings::ndb_note }; 85 if new_ptr.is_null() { 86 panic!("Failed to allocate memory for cloned note"); 87 } 88 89 // Use memcpy to copy the memory 90 unsafe { 91 libc::memcpy( 92 new_ptr as *mut libc::c_void, 93 *ptr as *const libc::c_void, 94 *size, 95 ); 96 } 97 98 // Return a new Owned Note 99 Note::Owned { 100 ptr: new_ptr, 101 size: *size, 102 } 103 } 104 105 Note::Transactional { 106 ptr, 107 size, 108 key, 109 transaction, 110 } => Note::Transactional { 111 ptr: *ptr, 112 size: *size, 113 key: *key, 114 transaction, 115 }, 116 } 117 } 118 } 119 120 impl<'a> Note<'a> { 121 /// Constructs an owned `Note`. This note is a pointer to a note in 122 /// memory and will be free'd when [Drop]ped. You normally wouldn't 123 /// use this method directly, public consumer would use from_json instead. 124 /// 125 /// [Drop]: std::ops::Drop 126 #[allow(dead_code)] 127 pub(crate) fn new_owned(ptr: *mut bindings::ndb_note, size: usize) -> Note<'static> { 128 Note::Owned { ptr, size } 129 } 130 131 #[allow(dead_code)] 132 pub(crate) fn new_unowned(ptr: &'a bindings::ndb_note) -> Note<'a> { 133 Note::Unowned { ptr } 134 } 135 136 /// Constructs a `Note` in a transactional context. 137 /// Use [Note::new_transactional] to create a new transactional note. 138 /// You normally wouldn't use this method directly, it is used by 139 /// functions that get notes from the database like 140 /// [ndb_get_note_by_id] 141 pub(crate) fn new_transactional( 142 ptr: *mut bindings::ndb_note, 143 size: usize, 144 key: NoteKey, 145 transaction: &'a Transaction, 146 ) -> Note<'a> { 147 Note::Transactional { 148 ptr, 149 size, 150 key, 151 transaction, 152 } 153 } 154 155 #[inline] 156 pub fn txn(&'a self) -> Option<&'a Transaction> { 157 match self { 158 Note::Transactional { transaction, .. } => Some(transaction), 159 _ => None, 160 } 161 } 162 163 /// Returns a database cursor that iterates over all of the relays 164 /// that the note has been seen on 165 #[inline] 166 pub fn relays(&self, txn: &'a Transaction) -> NoteRelays<'a> { 167 let Some(note_key) = self.key() else { 168 return NoteRelays::empty(); 169 }; 170 171 NoteRelays::new(txn, note_key) 172 } 173 174 #[inline] 175 pub fn key(&self) -> Option<NoteKey> { 176 match self { 177 Note::Transactional { key, .. } => Some(NoteKey::new(key.as_u64())), 178 _ => None, 179 } 180 } 181 182 #[inline] 183 pub fn size(&self) -> usize { 184 match self { 185 Note::Owned { size, .. } => *size, 186 Note::Unowned { .. } => 0, 187 Note::Transactional { size, .. } => *size, 188 } 189 } 190 191 #[inline] 192 pub fn flags(&self) -> u16 { 193 unsafe { *bindings::ndb_note_flags(self.as_ptr()) } 194 } 195 196 #[inline] 197 pub fn is_rumor(&self) -> bool { 198 (self.flags() & (bindings::NDB_NOTE_FLAG_RUMOR as u16)) 199 == bindings::NDB_NOTE_FLAG_RUMOR as u16 200 } 201 202 #[inline] 203 pub fn rumor_giftwrap_id(&self) -> Option<&'a [u8; 32]> { 204 unsafe { 205 let ptr = bindings::ndb_note_rumor_giftwrap_id(self.as_ptr()); 206 207 if ptr.is_null() { 208 return None; 209 } 210 211 Some(&*(ptr as *const [u8; 32])) 212 } 213 } 214 215 #[inline] 216 pub fn rumor_receiver_pubkey(&self) -> Option<&'a [u8; 32]> { 217 unsafe { 218 let ptr = bindings::ndb_note_rumor_receiver_pubkey(self.as_ptr()); 219 220 if ptr.is_null() { 221 return None; 222 } 223 224 Some(&*(ptr as *const [u8; 32])) 225 } 226 } 227 228 #[inline] 229 pub fn as_ptr(&self) -> *mut bindings::ndb_note { 230 match self { 231 Note::Owned { ptr, .. } => *ptr, 232 Note::Unowned { ptr } => *ptr as *const bindings::ndb_note as *mut bindings::ndb_note, 233 Note::Transactional { ptr, .. } => *ptr, 234 } 235 } 236 237 #[inline] 238 pub fn json_with_bufsize(&self, bufsize: usize) -> Result<String, Error> { 239 let mut buf = Vec::with_capacity(bufsize); 240 unsafe { 241 let size = bindings::ndb_note_json( 242 self.as_ptr(), 243 buf.as_mut_ptr() as *mut ::std::os::raw::c_char, 244 bufsize as ::std::os::raw::c_int, 245 ) as usize; 246 247 // Step 4: Check the return value for success 248 if size == 0 { 249 return Err(Error::BufferOverflow); // Handle the error appropriately 250 } 251 252 buf.set_len(size); 253 254 Ok(std::str::from_utf8_unchecked(&buf[..size - 1]).to_string()) 255 } 256 } 257 258 #[inline] 259 pub fn json(&self) -> Result<String, Error> { 260 // 1mb buffer 261 self.json_with_bufsize(1024usize * 1024usize) 262 } 263 264 #[inline] 265 fn content_size(&self) -> usize { 266 unsafe { bindings::ndb_note_content_length(self.as_ptr()) as usize } 267 } 268 269 #[inline] 270 pub fn created_at(&self) -> u64 { 271 unsafe { bindings::ndb_note_created_at(self.as_ptr()).into() } 272 } 273 274 #[inline] 275 pub fn content_ptr(&self) -> *const ::std::os::raw::c_char { 276 unsafe { bindings::ndb_note_content(self.as_ptr()) } 277 } 278 279 /// Get the [`Note`] contents. 280 #[inline] 281 pub fn content(&self) -> &'a str { 282 unsafe { 283 let content = self.content_ptr(); 284 let byte_slice = std::slice::from_raw_parts(content as *const u8, self.content_size()); 285 std::str::from_utf8_unchecked(byte_slice) 286 } 287 } 288 289 /// Get the note pubkey 290 #[inline] 291 pub fn pubkey(&self) -> &'a [u8; 32] { 292 unsafe { 293 let ptr = bindings::ndb_note_pubkey(self.as_ptr()); 294 &*(ptr as *const [u8; 32]) 295 } 296 } 297 298 #[inline] 299 pub fn id(&self) -> &'a [u8; 32] { 300 unsafe { 301 let ptr = bindings::ndb_note_id(self.as_ptr()); 302 &*(ptr as *const [u8; 32]) 303 } 304 } 305 306 #[inline] 307 pub fn kind(&self) -> u32 { 308 unsafe { bindings::ndb_note_kind(self.as_ptr()) } 309 } 310 311 #[inline] 312 pub fn tags(&self) -> Tags<'a> { 313 let tags = unsafe { bindings::ndb_note_tags(self.as_ptr()) }; 314 Tags::new(tags, self.clone()) 315 } 316 317 #[inline] 318 pub fn sig(&self) -> &'a [u8; 64] { 319 unsafe { 320 let ptr = bindings::ndb_note_sig(self.as_ptr()); 321 &*(ptr as *const [u8; 64]) 322 } 323 } 324 } 325 326 impl Drop for Note<'_> { 327 fn drop(&mut self) { 328 if let Note::Owned { ptr, .. } = self { 329 unsafe { libc::free((*ptr) as *mut libc::c_void) } 330 } 331 } 332 } 333 334 impl bindings::ndb_builder { 335 fn as_mut_ptr(&mut self) -> *mut bindings::ndb_builder { 336 self as *mut bindings::ndb_builder 337 } 338 } 339 340 impl bindings::ndb_keypair { 341 fn as_mut_ptr(&mut self) -> *mut bindings::ndb_keypair { 342 self as *mut bindings::ndb_keypair 343 } 344 } 345 346 impl Default for bindings::ndb_keypair { 347 fn default() -> Self { 348 bindings::ndb_keypair { 349 pubkey: [0; 32], 350 secret: [0; 32], 351 pair: [0; 96], 352 } 353 } 354 } 355 356 impl Default for bindings::ndb_builder { 357 fn default() -> Self { 358 bindings::ndb_builder { 359 mem: bindings::cursor::default(), 360 note_cur: bindings::cursor::default(), 361 strings: bindings::cursor::default(), 362 str_indices: bindings::cursor::default(), 363 note: std::ptr::null_mut(), 364 current_tag: std::ptr::null_mut(), 365 } 366 } 367 } 368 369 impl Default for bindings::cursor { 370 fn default() -> Self { 371 Self { 372 start: std::ptr::null_mut(), 373 p: std::ptr::null_mut(), 374 end: std::ptr::null_mut(), 375 } 376 } 377 } 378 379 pub struct NoteBuilder<'a> { 380 buffer: *mut ::std::os::raw::c_uchar, 381 builder: bindings::ndb_builder, 382 options: NoteBuildOptions<'a>, 383 } 384 385 impl Default for NoteBuilder<'_> { 386 fn default() -> Self { 387 NoteBuilder::new() 388 } 389 } 390 391 impl<'a> NoteBuilder<'a> { 392 pub fn with_bufsize(size: usize) -> Option<Self> { 393 let buffer: *mut c_uchar = unsafe { libc::malloc(size as libc::size_t) as *mut c_uchar }; 394 if buffer.is_null() { 395 return None; 396 } 397 398 let mut builder = NoteBuilder { 399 buffer, 400 options: NoteBuildOptions::default(), 401 builder: bindings::ndb_builder::default(), 402 }; 403 404 let ok = unsafe { 405 bindings::ndb_builder_init(builder.builder.as_mut_ptr(), builder.buffer, size) != 0 406 }; 407 408 if !ok { 409 // this really shouldn't happen 410 return None; 411 } 412 413 Some(builder) 414 } 415 416 /// Create a note builder with a 1mb buffer, if you need bigger notes 417 /// then use with_bufsize with a custom buffer size 418 pub fn new() -> Self { 419 let default_bufsize = 1024usize * 1024usize; 420 Self::with_bufsize(default_bufsize).expect("OOM when creating NoteBuilder") 421 } 422 423 pub fn as_mut_ptr(&mut self) -> *mut bindings::ndb_builder { 424 &mut self.builder as *mut bindings::ndb_builder 425 } 426 427 pub fn sig(mut self, signature: &[u8; 64]) -> Self { 428 self.options.sign_key = None; 429 unsafe { 430 bindings::ndb_builder_set_sig(self.as_mut_ptr(), signature.as_ptr() as *mut c_uchar) 431 }; 432 self 433 } 434 435 pub fn id(mut self, id: &[u8; 32]) -> Self { 436 unsafe { bindings::ndb_builder_set_id(self.as_mut_ptr(), id.as_ptr() as *mut c_uchar) }; 437 self 438 } 439 440 pub fn content(mut self, content: &str) -> Self { 441 unsafe { 442 // Call the external C function with the appropriate arguments 443 bindings::ndb_builder_set_content( 444 self.as_mut_ptr(), 445 content.as_ptr() as *const ::std::os::raw::c_char, 446 content.len() as ::std::os::raw::c_int, 447 ); 448 } 449 self 450 } 451 452 pub fn created_at(mut self, created_at: u64) -> Self { 453 self.options.set_created_at = false; 454 self.set_created_at(created_at); 455 self 456 } 457 458 pub fn kind(mut self, kind: u32) -> Self { 459 unsafe { 460 bindings::ndb_builder_set_kind(self.as_mut_ptr(), kind); 461 }; 462 self 463 } 464 465 pub fn pubkey(mut self, pubkey: &[u8; 32]) -> Self { 466 self.set_pubkey(pubkey); 467 self 468 } 469 470 fn set_pubkey(&mut self, pubkey: &[u8; 32]) { 471 unsafe { 472 bindings::ndb_builder_set_pubkey(self.as_mut_ptr(), pubkey.as_ptr() as *mut c_uchar) 473 }; 474 } 475 476 fn set_created_at(&mut self, created_at: u64) { 477 unsafe { 478 bindings::ndb_builder_set_created_at(self.as_mut_ptr(), created_at); 479 }; 480 } 481 482 pub fn start_tag(mut self) -> Self { 483 unsafe { 484 bindings::ndb_builder_new_tag(self.as_mut_ptr()); 485 }; 486 self 487 } 488 489 pub fn tag_str(mut self, str: &str) -> Self { 490 unsafe { 491 // Call the external C function with the appropriate arguments 492 bindings::ndb_builder_push_tag_str( 493 self.as_mut_ptr(), 494 str.as_ptr() as *const ::std::os::raw::c_char, 495 str.len() as ::std::os::raw::c_int, 496 ); 497 } 498 self 499 } 500 501 /// Push a packed 32-byte id. tag_str also does this for you, 502 /// but we expose a method here so that you don't have to 503 /// hex encode an id if you already have one 504 pub fn tag_id(mut self, id: &[u8; 32]) -> Self { 505 unsafe { 506 // Call the external C function with the appropriate arguments 507 bindings::ndb_builder_push_tag_id( 508 self.as_mut_ptr(), 509 id.as_ptr() as *mut ::std::os::raw::c_uchar, 510 ); 511 } 512 self 513 } 514 515 pub fn options(mut self, options: NoteBuildOptions<'a>) -> NoteBuilder<'a> { 516 self.options = options; 517 self 518 } 519 520 pub fn sign(mut self, seckey: &'a [u8; 32]) -> NoteBuilder<'a> { 521 self.options = self.options.sign(seckey); 522 self 523 } 524 525 pub fn build(&mut self) -> Option<Note<'static>> { 526 let mut note_ptr: *mut bindings::ndb_note = std::ptr::null_mut(); 527 let mut keypair = bindings::ndb_keypair::default(); 528 529 if self.options.set_created_at { 530 let start = std::time::SystemTime::now(); 531 if let Ok(since_the_epoch) = start.duration_since(std::time::UNIX_EPOCH) { 532 let timestamp = since_the_epoch.as_secs(); 533 self.set_created_at(timestamp); 534 } else { 535 return None; 536 } 537 } 538 539 let keypair_ptr = if let Some(sec) = self.options.sign_key { 540 keypair.secret.copy_from_slice(sec); 541 let ok = unsafe { bindings::ndb_create_keypair(keypair.as_mut_ptr()) != 0 }; 542 if ok { 543 // if we're signing, we should set the pubkey as well 544 self.set_pubkey(&keypair.pubkey); 545 keypair.as_mut_ptr() 546 } else { 547 std::ptr::null_mut() 548 } 549 } else { 550 std::ptr::null_mut() 551 }; 552 553 let size = unsafe { 554 bindings::ndb_builder_finalize( 555 self.as_mut_ptr(), 556 &mut note_ptr as *mut *mut bindings::ndb_note, 557 keypair_ptr, 558 ) as usize 559 }; 560 561 if size == 0 { 562 return None; 563 } 564 565 note_ptr = unsafe { 566 libc::realloc(note_ptr as *mut libc::c_void, size) as *mut bindings::ndb_note 567 }; 568 569 if note_ptr.is_null() { 570 return None; 571 } 572 573 Some(Note::new_owned(note_ptr, size)) 574 } 575 } 576 577 #[cfg(test)] 578 mod tests { 579 use super::*; 580 581 #[test] 582 fn note_query_works() { 583 use crate::config::Config; 584 use crate::error::Error; 585 use crate::ndb::Ndb; 586 use crate::test_util; 587 588 let db = "target/testdbs/note_query_works"; 589 590 // Initialize ndb 591 { 592 let cfg = Config::new(); 593 let ndb = Ndb::new(&db, &cfg).expect("db open"); 594 let mut txn = Transaction::new(&ndb).expect("new txn"); 595 596 let err = ndb 597 .get_note_by_id(&mut txn, &[0; 32]) 598 .expect_err("not found"); 599 assert!(matches!(err, Error::NotFound)); 600 } 601 602 test_util::cleanup_db(db); 603 } 604 605 #[test] 606 fn note_builder_works() { 607 let pubkey: [u8; 32] = [ 608 0x6c, 0x54, 0x0e, 0xd0, 0x60, 0xbf, 0xc2, 0xb0, 0xc5, 0xb6, 0xf0, 0x9c, 0xd3, 0xeb, 609 0xed, 0xf9, 0x80, 0xef, 0x7b, 0xc8, 0x36, 0xd6, 0x95, 0x82, 0x36, 0x1d, 0x20, 0xf2, 610 0xad, 0x12, 0x4f, 0x23, 611 ]; 612 613 let seckey: [u8; 32] = [ 614 0xd8, 0x62, 0x2e, 0x92, 0x47, 0xab, 0x39, 0x30, 0x11, 0x7e, 0x66, 0x45, 0xd5, 0xf7, 615 0x8b, 0x66, 0xbd, 0xd3, 0xaf, 0xe2, 0x46, 0x4f, 0x90, 0xbc, 0xd9, 0xe0, 0x38, 0x75, 616 0x8d, 0x2d, 0x55, 0x34, 617 ]; 618 619 let id: [u8; 32] = [ 620 0xfb, 0x16, 0x5b, 0xe2, 0x2c, 0x7b, 0x25, 0x18, 0xb7, 0x49, 0xaa, 0xbb, 0x71, 0x40, 621 0xc7, 0x3f, 0x08, 0x87, 0xfe, 0x84, 0x47, 0x5c, 0x82, 0x78, 0x57, 0x00, 0x66, 0x3b, 622 0xe8, 0x5b, 0xa8, 0x59, 623 ]; 624 625 let note = NoteBuilder::new() 626 .kind(1) 627 .content("this is the content") 628 .created_at(42) 629 .start_tag() 630 .tag_str("comment") 631 .tag_str("this is a comment") 632 .start_tag() 633 .tag_str("blah") 634 .tag_str("something") 635 .sign(&seckey) 636 .build() 637 .expect("expected build to work"); 638 639 assert_eq!(note.created_at(), 42); 640 assert_eq!(note.content(), "this is the content"); 641 assert_eq!(note.kind(), 1); 642 assert_eq!(note.pubkey(), &pubkey); 643 assert!(note.sig() != &[0; 64]); 644 assert_eq!(note.id(), &id); 645 646 for tag in note.tags() { 647 assert_eq!(tag.get_unchecked(0).variant().str().unwrap(), "comment"); 648 assert_eq!( 649 tag.get_unchecked(1).variant().str().unwrap(), 650 "this is a comment" 651 ); 652 break; 653 } 654 655 for tag in note.tags().iter().skip(1) { 656 assert_eq!(tag.get_unchecked(0).variant().str().unwrap(), "blah"); 657 assert_eq!(tag.get_unchecked(1).variant().str().unwrap(), "something"); 658 break; 659 } 660 661 let json = note.json().expect("note json"); 662 // the signature changes so 267 is everything up until the signature 663 assert_eq!( 664 &json[..267], 665 "{\"id\":\"fb165be22c7b2518b749aabb7140c73f0887fe84475c82785700663be85ba859\",\"pubkey\":\"6c540ed060bfc2b0c5b6f09cd3ebedf980ef7bc836d69582361d20f2ad124f23\",\"created_at\":42,\"kind\":1,\"tags\":[[\"comment\",\"this is a comment\"],[\"blah\",\"something\"]],\"content\":\"this is the content\"" 666 ); 667 } 668 }