nostrdb-rs

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

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 }