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