nostrdb-rs

nostrdb in rust!
git clone git://jb55.com/nostrdb-rs
Log | Files | Refs | Submodules | README | LICENSE

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 }