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