nostrdb-rs

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

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 }