nostrdb-rs

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

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 }