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