note.rs (18716B)
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 as_ptr(&self) -> *mut bindings::ndb_note { 193 match self { 194 Note::Owned { ptr, .. } => *ptr, 195 Note::Unowned { ptr } => *ptr as *const bindings::ndb_note as *mut bindings::ndb_note, 196 Note::Transactional { ptr, .. } => *ptr, 197 } 198 } 199 200 #[inline] 201 pub fn json_with_bufsize(&self, bufsize: usize) -> Result<String, Error> { 202 let mut buf = Vec::with_capacity(bufsize); 203 unsafe { 204 let size = bindings::ndb_note_json( 205 self.as_ptr(), 206 buf.as_mut_ptr() as *mut ::std::os::raw::c_char, 207 bufsize as ::std::os::raw::c_int, 208 ) as usize; 209 210 // Step 4: Check the return value for success 211 if size == 0 { 212 return Err(Error::BufferOverflow); // Handle the error appropriately 213 } 214 215 buf.set_len(size); 216 217 Ok(std::str::from_utf8_unchecked(&buf[..size - 1]).to_string()) 218 } 219 } 220 221 #[inline] 222 pub fn json(&self) -> Result<String, Error> { 223 // 1mb buffer 224 self.json_with_bufsize(1024usize * 1024usize) 225 } 226 227 #[inline] 228 fn content_size(&self) -> usize { 229 unsafe { bindings::ndb_note_content_length(self.as_ptr()) as usize } 230 } 231 232 #[inline] 233 pub fn created_at(&self) -> u64 { 234 unsafe { bindings::ndb_note_created_at(self.as_ptr()).into() } 235 } 236 237 #[inline] 238 pub fn content_ptr(&self) -> *const ::std::os::raw::c_char { 239 unsafe { bindings::ndb_note_content(self.as_ptr()) } 240 } 241 242 /// Get the [`Note`] contents. 243 #[inline] 244 pub fn content(&self) -> &'a str { 245 unsafe { 246 let content = self.content_ptr(); 247 let byte_slice = std::slice::from_raw_parts(content as *const u8, self.content_size()); 248 std::str::from_utf8_unchecked(byte_slice) 249 } 250 } 251 252 /// Get the note pubkey 253 #[inline] 254 pub fn pubkey(&self) -> &'a [u8; 32] { 255 unsafe { 256 let ptr = bindings::ndb_note_pubkey(self.as_ptr()); 257 &*(ptr as *const [u8; 32]) 258 } 259 } 260 261 #[inline] 262 pub fn id(&self) -> &'a [u8; 32] { 263 unsafe { 264 let ptr = bindings::ndb_note_id(self.as_ptr()); 265 &*(ptr as *const [u8; 32]) 266 } 267 } 268 269 #[inline] 270 pub fn kind(&self) -> u32 { 271 unsafe { bindings::ndb_note_kind(self.as_ptr()) } 272 } 273 274 #[inline] 275 pub fn tags(&self) -> Tags<'a> { 276 let tags = unsafe { bindings::ndb_note_tags(self.as_ptr()) }; 277 Tags::new(tags, self.clone()) 278 } 279 280 #[inline] 281 pub fn sig(&self) -> &'a [u8; 64] { 282 unsafe { 283 let ptr = bindings::ndb_note_sig(self.as_ptr()); 284 &*(ptr as *const [u8; 64]) 285 } 286 } 287 } 288 289 impl Drop for Note<'_> { 290 fn drop(&mut self) { 291 if let Note::Owned { ptr, .. } = self { 292 unsafe { libc::free((*ptr) as *mut libc::c_void) } 293 } 294 } 295 } 296 297 impl bindings::ndb_builder { 298 fn as_mut_ptr(&mut self) -> *mut bindings::ndb_builder { 299 self as *mut bindings::ndb_builder 300 } 301 } 302 303 impl bindings::ndb_keypair { 304 fn as_mut_ptr(&mut self) -> *mut bindings::ndb_keypair { 305 self as *mut bindings::ndb_keypair 306 } 307 } 308 309 impl Default for bindings::ndb_keypair { 310 fn default() -> Self { 311 bindings::ndb_keypair { 312 pubkey: [0; 32], 313 secret: [0; 32], 314 pair: [0; 96], 315 } 316 } 317 } 318 319 impl Default for bindings::ndb_builder { 320 fn default() -> Self { 321 bindings::ndb_builder { 322 mem: bindings::cursor::default(), 323 note_cur: bindings::cursor::default(), 324 strings: bindings::cursor::default(), 325 str_indices: bindings::cursor::default(), 326 note: std::ptr::null_mut(), 327 current_tag: std::ptr::null_mut(), 328 } 329 } 330 } 331 332 impl Default for bindings::cursor { 333 fn default() -> Self { 334 Self { 335 start: std::ptr::null_mut(), 336 p: std::ptr::null_mut(), 337 end: std::ptr::null_mut(), 338 } 339 } 340 } 341 342 pub struct NoteBuilder<'a> { 343 buffer: *mut ::std::os::raw::c_uchar, 344 builder: bindings::ndb_builder, 345 options: NoteBuildOptions<'a>, 346 } 347 348 impl Default for NoteBuilder<'_> { 349 fn default() -> Self { 350 NoteBuilder::new() 351 } 352 } 353 354 impl<'a> NoteBuilder<'a> { 355 pub fn with_bufsize(size: usize) -> Option<Self> { 356 let buffer: *mut c_uchar = unsafe { libc::malloc(size as libc::size_t) as *mut c_uchar }; 357 if buffer.is_null() { 358 return None; 359 } 360 361 let mut builder = NoteBuilder { 362 buffer, 363 options: NoteBuildOptions::default(), 364 builder: bindings::ndb_builder::default(), 365 }; 366 367 let ok = unsafe { 368 bindings::ndb_builder_init(builder.builder.as_mut_ptr(), builder.buffer, size) != 0 369 }; 370 371 if !ok { 372 // this really shouldn't happen 373 return None; 374 } 375 376 Some(builder) 377 } 378 379 /// Create a note builder with a 1mb buffer, if you need bigger notes 380 /// then use with_bufsize with a custom buffer size 381 pub fn new() -> Self { 382 let default_bufsize = 1024usize * 1024usize; 383 Self::with_bufsize(default_bufsize).expect("OOM when creating NoteBuilder") 384 } 385 386 pub fn as_mut_ptr(&mut self) -> *mut bindings::ndb_builder { 387 &mut self.builder as *mut bindings::ndb_builder 388 } 389 390 pub fn sig(mut self, signature: &[u8; 64]) -> Self { 391 self.options.sign_key = None; 392 unsafe { 393 bindings::ndb_builder_set_sig(self.as_mut_ptr(), signature.as_ptr() as *mut c_uchar) 394 }; 395 self 396 } 397 398 pub fn id(mut self, id: &[u8; 32]) -> Self { 399 unsafe { bindings::ndb_builder_set_id(self.as_mut_ptr(), id.as_ptr() as *mut c_uchar) }; 400 self 401 } 402 403 pub fn content(mut self, content: &str) -> Self { 404 unsafe { 405 // Call the external C function with the appropriate arguments 406 bindings::ndb_builder_set_content( 407 self.as_mut_ptr(), 408 content.as_ptr() as *const ::std::os::raw::c_char, 409 content.len() as ::std::os::raw::c_int, 410 ); 411 } 412 self 413 } 414 415 pub fn created_at(mut self, created_at: u64) -> Self { 416 self.options.set_created_at = false; 417 self.set_created_at(created_at); 418 self 419 } 420 421 pub fn kind(mut self, kind: u32) -> Self { 422 unsafe { 423 bindings::ndb_builder_set_kind(self.as_mut_ptr(), kind); 424 }; 425 self 426 } 427 428 pub fn pubkey(mut self, pubkey: &[u8; 32]) -> Self { 429 self.set_pubkey(pubkey); 430 self 431 } 432 433 fn set_pubkey(&mut self, pubkey: &[u8; 32]) { 434 unsafe { 435 bindings::ndb_builder_set_pubkey(self.as_mut_ptr(), pubkey.as_ptr() as *mut c_uchar) 436 }; 437 } 438 439 fn set_created_at(&mut self, created_at: u64) { 440 unsafe { 441 bindings::ndb_builder_set_created_at(self.as_mut_ptr(), created_at); 442 }; 443 } 444 445 pub fn start_tag(mut self) -> Self { 446 unsafe { 447 bindings::ndb_builder_new_tag(self.as_mut_ptr()); 448 }; 449 self 450 } 451 452 pub fn tag_str(mut self, str: &str) -> Self { 453 unsafe { 454 // Call the external C function with the appropriate arguments 455 bindings::ndb_builder_push_tag_str( 456 self.as_mut_ptr(), 457 str.as_ptr() as *const ::std::os::raw::c_char, 458 str.len() as ::std::os::raw::c_int, 459 ); 460 } 461 self 462 } 463 464 /// Push a packed 32-byte id. tag_str also does this for you, 465 /// but we expose a method here so that you don't have to 466 /// hex encode an id if you already have one 467 pub fn tag_id(mut self, id: &[u8; 32]) -> Self { 468 unsafe { 469 // Call the external C function with the appropriate arguments 470 bindings::ndb_builder_push_tag_id( 471 self.as_mut_ptr(), 472 id.as_ptr() as *mut ::std::os::raw::c_uchar, 473 ); 474 } 475 self 476 } 477 478 pub fn options(mut self, options: NoteBuildOptions<'a>) -> NoteBuilder<'a> { 479 self.options = options; 480 self 481 } 482 483 pub fn sign(mut self, seckey: &'a [u8; 32]) -> NoteBuilder<'a> { 484 self.options = self.options.sign(seckey); 485 self 486 } 487 488 pub fn build(&mut self) -> Option<Note<'static>> { 489 let mut note_ptr: *mut bindings::ndb_note = std::ptr::null_mut(); 490 let mut keypair = bindings::ndb_keypair::default(); 491 492 if self.options.set_created_at { 493 let start = std::time::SystemTime::now(); 494 if let Ok(since_the_epoch) = start.duration_since(std::time::UNIX_EPOCH) { 495 let timestamp = since_the_epoch.as_secs(); 496 self.set_created_at(timestamp); 497 } else { 498 return None; 499 } 500 } 501 502 let keypair_ptr = if let Some(sec) = self.options.sign_key { 503 keypair.secret.copy_from_slice(sec); 504 let ok = unsafe { bindings::ndb_create_keypair(keypair.as_mut_ptr()) != 0 }; 505 if ok { 506 // if we're signing, we should set the pubkey as well 507 self.set_pubkey(&keypair.pubkey); 508 keypair.as_mut_ptr() 509 } else { 510 std::ptr::null_mut() 511 } 512 } else { 513 std::ptr::null_mut() 514 }; 515 516 let size = unsafe { 517 bindings::ndb_builder_finalize( 518 self.as_mut_ptr(), 519 &mut note_ptr as *mut *mut bindings::ndb_note, 520 keypair_ptr, 521 ) as usize 522 }; 523 524 if size == 0 { 525 return None; 526 } 527 528 note_ptr = unsafe { 529 libc::realloc(note_ptr as *mut libc::c_void, size) as *mut bindings::ndb_note 530 }; 531 532 if note_ptr.is_null() { 533 return None; 534 } 535 536 Some(Note::new_owned(note_ptr, size)) 537 } 538 } 539 540 #[cfg(test)] 541 mod tests { 542 use super::*; 543 544 #[test] 545 fn note_query_works() { 546 use crate::config::Config; 547 use crate::error::Error; 548 use crate::ndb::Ndb; 549 use crate::test_util; 550 551 let db = "target/testdbs/note_query_works"; 552 553 // Initialize ndb 554 { 555 let cfg = Config::new(); 556 let ndb = Ndb::new(&db, &cfg).expect("db open"); 557 let mut txn = Transaction::new(&ndb).expect("new txn"); 558 559 let err = ndb 560 .get_note_by_id(&mut txn, &[0; 32]) 561 .expect_err("not found"); 562 assert!(matches!(err, Error::NotFound)); 563 } 564 565 test_util::cleanup_db(db); 566 } 567 568 #[test] 569 fn note_builder_works() { 570 let pubkey: [u8; 32] = [ 571 0x6c, 0x54, 0x0e, 0xd0, 0x60, 0xbf, 0xc2, 0xb0, 0xc5, 0xb6, 0xf0, 0x9c, 0xd3, 0xeb, 572 0xed, 0xf9, 0x80, 0xef, 0x7b, 0xc8, 0x36, 0xd6, 0x95, 0x82, 0x36, 0x1d, 0x20, 0xf2, 573 0xad, 0x12, 0x4f, 0x23, 574 ]; 575 576 let seckey: [u8; 32] = [ 577 0xd8, 0x62, 0x2e, 0x92, 0x47, 0xab, 0x39, 0x30, 0x11, 0x7e, 0x66, 0x45, 0xd5, 0xf7, 578 0x8b, 0x66, 0xbd, 0xd3, 0xaf, 0xe2, 0x46, 0x4f, 0x90, 0xbc, 0xd9, 0xe0, 0x38, 0x75, 579 0x8d, 0x2d, 0x55, 0x34, 580 ]; 581 582 let id: [u8; 32] = [ 583 0xfb, 0x16, 0x5b, 0xe2, 0x2c, 0x7b, 0x25, 0x18, 0xb7, 0x49, 0xaa, 0xbb, 0x71, 0x40, 584 0xc7, 0x3f, 0x08, 0x87, 0xfe, 0x84, 0x47, 0x5c, 0x82, 0x78, 0x57, 0x00, 0x66, 0x3b, 585 0xe8, 0x5b, 0xa8, 0x59, 586 ]; 587 588 let note = NoteBuilder::new() 589 .kind(1) 590 .content("this is the content") 591 .created_at(42) 592 .start_tag() 593 .tag_str("comment") 594 .tag_str("this is a comment") 595 .start_tag() 596 .tag_str("blah") 597 .tag_str("something") 598 .sign(&seckey) 599 .build() 600 .expect("expected build to work"); 601 602 assert_eq!(note.created_at(), 42); 603 assert_eq!(note.content(), "this is the content"); 604 assert_eq!(note.kind(), 1); 605 assert_eq!(note.pubkey(), &pubkey); 606 assert!(note.sig() != &[0; 64]); 607 assert_eq!(note.id(), &id); 608 609 for tag in note.tags() { 610 assert_eq!(tag.get_unchecked(0).variant().str().unwrap(), "comment"); 611 assert_eq!( 612 tag.get_unchecked(1).variant().str().unwrap(), 613 "this is a comment" 614 ); 615 break; 616 } 617 618 for tag in note.tags().iter().skip(1) { 619 assert_eq!(tag.get_unchecked(0).variant().str().unwrap(), "blah"); 620 assert_eq!(tag.get_unchecked(1).variant().str().unwrap(), "something"); 621 break; 622 } 623 624 let json = note.json().expect("note json"); 625 // the signature changes so 267 is everything up until the signature 626 assert_eq!(&json[..267], "{\"id\":\"fb165be22c7b2518b749aabb7140c73f0887fe84475c82785700663be85ba859\",\"pubkey\":\"6c540ed060bfc2b0c5b6f09cd3ebedf980ef7bc836d69582361d20f2ad124f23\",\"created_at\":42,\"kind\":1,\"tags\":[[\"comment\",\"this is a comment\"],[\"blah\",\"something\"]],\"content\":\"this is the content\""); 627 } 628 }