nostrdb-rs

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

note.rs (15723B)


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