commit b59268afde43cbf0c8f9f1ae584679ea82191a11
parent 31ea02a27a384618b44e02f38e6176fc4040c982
Author: William Casarin <jb55@jb55.com>
Date: Fri, 28 Jun 2024 10:55:58 -0700
Add note builder api
Fixes: https://github.com/damus-io/nostrdb-rs/issues/10
Changelog-Fixed: Fixed double-free issue for owned notes
Changelog-Added: Added note builder interface
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
6 files changed, 353 insertions(+), 33 deletions(-)
diff --git a/shell.nix b/shell.nix
@@ -1,7 +1,7 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
mkShell {
- nativeBuildInputs = [ rustPlatform.bindgenHook cargo clippy rustc rustfmt libiconv pkg-config valgrind ];
+ nativeBuildInputs = [ rustPlatform.bindgenHook cargo clippy rustc rustfmt libiconv pkg-config valgrind gdb ];
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
}
diff --git a/src/lib.rs b/src/lib.rs
@@ -33,7 +33,7 @@ pub use filter::Filter;
pub use ndb::Ndb;
pub use ndb_profile::{NdbProfile, NdbProfileRecord};
pub use ndb_str::{NdbStr, NdbStrVariant};
-pub use note::{Note, NoteKey};
+pub use note::{Note, NoteBuildOptions, NoteBuilder, NoteKey};
pub use profile::{ProfileKey, ProfileRecord};
pub use query::QueryResult;
pub use result::Result;
diff --git a/src/ndb_str.rs b/src/ndb_str.rs
@@ -2,7 +2,7 @@ use crate::{bindings, Note};
pub struct NdbStr<'a> {
ndb_str: bindings::ndb_str,
- note: Note<'a>,
+ note: &'a Note<'a>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -39,10 +39,10 @@ impl bindings::ndb_str {
impl<'a> NdbStr<'a> {
pub fn note(&self) -> &Note<'a> {
- &self.note
+ self.note
}
- pub(crate) fn new(ndb_str: bindings::ndb_str, note: Note<'a>) -> Self {
+ pub(crate) fn new(ndb_str: bindings::ndb_str, note: &'a Note<'a>) -> Self {
NdbStr { ndb_str, note }
}
diff --git a/src/note.rs b/src/note.rs
@@ -1,6 +1,7 @@
use crate::bindings;
use crate::tags::Tags;
use crate::transaction::Transaction;
+use ::std::os::raw::c_uchar;
use std::hash::Hash;
#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)]
@@ -16,6 +17,35 @@ impl NoteKey {
}
}
+pub struct NoteBuildOptions<'a> {
+ /// Generate the created_at based on the current time, otherwise the id field will remain untouched
+ pub set_created_at: bool,
+
+ /// Sign with the secret key, otherwise sig field will remain untouched
+ pub sign_key: Option<&'a [u8; 32]>,
+}
+
+impl<'a> Default for NoteBuildOptions<'a> {
+ fn default() -> Self {
+ NoteBuildOptions {
+ set_created_at: true,
+ sign_key: None,
+ }
+ }
+}
+
+impl<'a> NoteBuildOptions<'a> {
+ pub fn created_at(mut self, set_created_at: bool) -> Self {
+ self.set_created_at = set_created_at;
+ self
+ }
+
+ pub fn sign(mut self, seckey: &'a [u8; 32]) -> NoteBuildOptions<'a> {
+ self.sign_key = Some(seckey);
+ self
+ }
+}
+
#[derive(Debug, Clone)]
pub enum Note<'a> {
/// A note in-memory outside of nostrdb. This note is a pointer to a note in
@@ -97,6 +127,12 @@ impl<'a> Note<'a> {
}
}
+ /*
+ pub fn json() -> String {
+ unsafe { bindings::ndb_note_json() }
+ }
+ */
+
fn content_size(&self) -> usize {
unsafe { bindings::ndb_note_content_length(self.as_ptr()) as usize }
}
@@ -137,9 +173,9 @@ impl<'a> Note<'a> {
unsafe { bindings::ndb_note_kind(self.as_ptr()) }
}
- pub fn tags(&self) -> Tags<'a> {
+ pub fn tags(&'a self) -> Tags<'a> {
let tags = unsafe { bindings::ndb_note_tags(self.as_ptr()) };
- Tags::new(tags, self.clone())
+ Tags::new(tags, self)
}
pub fn sig(&self) -> &'a [u8; 64] {
@@ -158,6 +194,235 @@ impl<'a> Drop for Note<'a> {
}
}
+impl bindings::ndb_builder {
+ fn as_mut_ptr(&mut self) -> *mut bindings::ndb_builder {
+ self as *mut bindings::ndb_builder
+ }
+}
+
+impl bindings::ndb_keypair {
+ fn as_mut_ptr(&mut self) -> *mut bindings::ndb_keypair {
+ self as *mut bindings::ndb_keypair
+ }
+}
+
+impl Default for bindings::ndb_keypair {
+ fn default() -> Self {
+ bindings::ndb_keypair {
+ pubkey: [0; 32],
+ secret: [0; 32],
+ pair: [0; 96],
+ }
+ }
+}
+
+impl Default for bindings::ndb_builder {
+ fn default() -> Self {
+ bindings::ndb_builder {
+ mem: bindings::cursor::default(),
+ note_cur: bindings::cursor::default(),
+ strings: bindings::cursor::default(),
+ str_indices: bindings::cursor::default(),
+ note: std::ptr::null_mut(),
+ current_tag: std::ptr::null_mut(),
+ }
+ }
+}
+
+impl Default for bindings::cursor {
+ fn default() -> Self {
+ Self {
+ start: std::ptr::null_mut(),
+ p: std::ptr::null_mut(),
+ end: std::ptr::null_mut(),
+ }
+ }
+}
+
+pub struct NoteBuilder<'a> {
+ buffer: *mut ::std::os::raw::c_uchar,
+ builder: bindings::ndb_builder,
+ options: NoteBuildOptions<'a>,
+}
+
+impl<'a> Default for NoteBuilder<'a> {
+ fn default() -> Self {
+ NoteBuilder::new()
+ }
+}
+
+impl<'a> NoteBuilder<'a> {
+ pub fn with_bufsize(size: usize) -> Option<Self> {
+ let buffer: *mut c_uchar = unsafe { libc::malloc(size as libc::size_t) as *mut c_uchar };
+ if buffer.is_null() {
+ return None;
+ }
+
+ let mut builder = NoteBuilder {
+ buffer,
+ options: NoteBuildOptions::default(),
+ builder: bindings::ndb_builder::default(),
+ };
+
+ let ok = unsafe {
+ bindings::ndb_builder_init(builder.builder.as_mut_ptr(), builder.buffer, size) != 0
+ };
+
+ if !ok {
+ // this really shouldn't happen
+ return None;
+ }
+
+ Some(builder)
+ }
+
+ /// Create a note builder with a 1mb buffer, if you need bigger notes
+ /// then use with_bufsize with a custom buffer size
+ pub fn new() -> Self {
+ let default_bufsize = 1024usize * 1024usize;
+ Self::with_bufsize(default_bufsize).expect("OOM when creating NoteBuilder")
+ }
+
+ pub fn as_mut_ptr(&mut self) -> *mut bindings::ndb_builder {
+ &mut self.builder as *mut bindings::ndb_builder
+ }
+
+ pub fn sig(mut self, signature: &[u8; 64]) -> Self {
+ self.options.sign_key = None;
+ unsafe {
+ bindings::ndb_builder_set_sig(self.as_mut_ptr(), signature.as_ptr() as *mut c_uchar)
+ };
+ self
+ }
+
+ pub fn id(mut self, id: &[u8; 32]) -> Self {
+ unsafe { bindings::ndb_builder_set_id(self.as_mut_ptr(), id.as_ptr() as *mut c_uchar) };
+ self
+ }
+
+ pub fn content(mut self, content: &str) -> Self {
+ unsafe {
+ // Call the external C function with the appropriate arguments
+ bindings::ndb_builder_set_content(
+ self.as_mut_ptr(),
+ content.as_ptr() as *const ::std::os::raw::c_char,
+ content.len() as ::std::os::raw::c_int,
+ );
+ }
+ self
+ }
+
+ pub fn created_at(mut self, created_at: u64) -> Self {
+ self.options.set_created_at = false;
+ self.set_created_at(created_at);
+ self
+ }
+
+ pub fn kind(mut self, kind: u32) -> Self {
+ unsafe {
+ bindings::ndb_builder_set_kind(self.as_mut_ptr(), kind);
+ };
+ self
+ }
+
+ pub fn pubkey(mut self, pubkey: &[u8; 32]) -> Self {
+ self.set_pubkey(pubkey);
+ self
+ }
+
+ fn set_pubkey(&mut self, pubkey: &[u8; 32]) {
+ unsafe {
+ bindings::ndb_builder_set_pubkey(self.as_mut_ptr(), pubkey.as_ptr() as *mut c_uchar)
+ };
+ }
+
+ fn set_created_at(&mut self, created_at: u64) {
+ unsafe {
+ bindings::ndb_builder_set_created_at(self.as_mut_ptr(), created_at);
+ };
+ }
+
+ pub fn start_tag(mut self) -> Self {
+ unsafe {
+ bindings::ndb_builder_new_tag(self.as_mut_ptr());
+ };
+ self
+ }
+
+ pub fn tag_str(mut self, str: &str) -> Self {
+ unsafe {
+ // Call the external C function with the appropriate arguments
+ bindings::ndb_builder_push_tag_str(
+ self.as_mut_ptr(),
+ str.as_ptr() as *const ::std::os::raw::c_char,
+ str.len() as ::std::os::raw::c_int,
+ );
+ }
+ self
+ }
+
+ pub fn options(mut self, options: NoteBuildOptions<'a>) -> NoteBuilder<'a> {
+ self.options = options;
+ self
+ }
+
+ pub fn sign(mut self, seckey: &'a [u8; 32]) -> NoteBuilder<'a> {
+ self.options = self.options.sign(seckey);
+ self
+ }
+
+ pub fn build(&mut self) -> Option<Note<'static>> {
+ let mut note_ptr: *mut bindings::ndb_note = std::ptr::null_mut();
+ let mut keypair = bindings::ndb_keypair::default();
+
+ if self.options.set_created_at {
+ let start = std::time::SystemTime::now();
+ if let Ok(since_the_epoch) = start.duration_since(std::time::UNIX_EPOCH) {
+ let timestamp = since_the_epoch.as_secs();
+ self.set_created_at(timestamp);
+ } else {
+ return None;
+ }
+ }
+
+ let keypair_ptr = if let Some(sec) = self.options.sign_key {
+ keypair.secret.copy_from_slice(sec);
+ let ok = unsafe { bindings::ndb_create_keypair(keypair.as_mut_ptr()) != 0 };
+ if ok {
+ // if we're signing, we should set the pubkey as well
+ self.set_pubkey(&keypair.pubkey);
+ keypair.as_mut_ptr()
+ } else {
+ std::ptr::null_mut()
+ }
+ } else {
+ std::ptr::null_mut()
+ };
+
+ let size = unsafe {
+ bindings::ndb_builder_finalize(
+ self.as_mut_ptr(),
+ &mut note_ptr as *mut *mut bindings::ndb_note,
+ keypair_ptr,
+ ) as usize
+ };
+
+ if size == 0 {
+ return None;
+ }
+
+ note_ptr = unsafe {
+ libc::realloc(note_ptr as *mut libc::c_void, size) as *mut bindings::ndb_note
+ };
+
+ if note_ptr.is_null() {
+ return None;
+ }
+
+ Some(Note::new_owned(note_ptr, size))
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -185,4 +450,61 @@ mod tests {
test_util::cleanup_db(db);
}
+
+ #[test]
+ fn note_builder_works() {
+ let pubkey: [u8; 32] = [
+ 0x6c, 0x54, 0x0e, 0xd0, 0x60, 0xbf, 0xc2, 0xb0, 0xc5, 0xb6, 0xf0, 0x9c, 0xd3, 0xeb,
+ 0xed, 0xf9, 0x80, 0xef, 0x7b, 0xc8, 0x36, 0xd6, 0x95, 0x82, 0x36, 0x1d, 0x20, 0xf2,
+ 0xad, 0x12, 0x4f, 0x23,
+ ];
+
+ let seckey: [u8; 32] = [
+ 0xd8, 0x62, 0x2e, 0x92, 0x47, 0xab, 0x39, 0x30, 0x11, 0x7e, 0x66, 0x45, 0xd5, 0xf7,
+ 0x8b, 0x66, 0xbd, 0xd3, 0xaf, 0xe2, 0x46, 0x4f, 0x90, 0xbc, 0xd9, 0xe0, 0x38, 0x75,
+ 0x8d, 0x2d, 0x55, 0x34,
+ ];
+
+ let id: [u8; 32] = [
+ 0xfb, 0x16, 0x5b, 0xe2, 0x2c, 0x7b, 0x25, 0x18, 0xb7, 0x49, 0xaa, 0xbb, 0x71, 0x40,
+ 0xc7, 0x3f, 0x08, 0x87, 0xfe, 0x84, 0x47, 0x5c, 0x82, 0x78, 0x57, 0x00, 0x66, 0x3b,
+ 0xe8, 0x5b, 0xa8, 0x59,
+ ];
+
+ let note = NoteBuilder::new()
+ .kind(1)
+ .content("this is the content")
+ .created_at(42)
+ .start_tag()
+ .tag_str("comment")
+ .tag_str("this is a comment")
+ .start_tag()
+ .tag_str("blah")
+ .tag_str("something")
+ .sign(&seckey)
+ .build()
+ .expect("expected build to work");
+
+ assert_eq!(note.created_at(), 42);
+ assert_eq!(note.content(), "this is the content");
+ assert_eq!(note.kind(), 1);
+ assert_eq!(note.pubkey(), &pubkey);
+ assert!(note.sig() != &[0; 64]);
+ assert_eq!(note.id(), &id);
+
+ for tag in note.tags() {
+ assert_eq!(tag.get_unchecked(0).variant().str().unwrap(), "comment");
+ assert_eq!(
+ tag.get_unchecked(1).variant().str().unwrap(),
+ "this is a comment"
+ );
+ break;
+ }
+
+ for tag in note.tags().iter().skip(1) {
+ assert_eq!(tag.get_unchecked(0).variant().str().unwrap(), "blah");
+ assert_eq!(tag.get_unchecked(1).variant().str().unwrap(), "something");
+ break;
+ }
+ }
}
diff --git a/src/tags.rs b/src/tags.rs
@@ -1,13 +1,13 @@
use crate::{bindings, NdbStr, Note};
#[derive(Debug, Clone)]
-pub struct Tag<'a> {
+pub struct Tag<'n> {
ptr: *mut bindings::ndb_tag,
- note: Note<'a>,
+ note: &'n Note<'n>,
}
-impl<'a> Tag<'a> {
- pub(crate) fn new(ptr: *mut bindings::ndb_tag, note: Note<'a>) -> Self {
+impl<'n> Tag<'n> {
+ pub(crate) fn new(ptr: *mut bindings::ndb_tag, note: &'n Note<'n>) -> Self {
Tag { ptr, note }
}
@@ -15,7 +15,7 @@ impl<'a> Tag<'a> {
unsafe { bindings::ndb_tag_count(self.as_ptr()) }
}
- pub fn get_unchecked(&self, ind: u16) -> NdbStr<'a> {
+ pub fn get_unchecked(&self, ind: u16) -> NdbStr<'n> {
let nstr = unsafe {
bindings::ndb_tag_str(
self.note().as_ptr(),
@@ -23,18 +23,18 @@ impl<'a> Tag<'a> {
ind as ::std::os::raw::c_int,
)
};
- NdbStr::new(nstr, self.note.clone())
+ NdbStr::new(nstr, self.note)
}
- pub fn get(&self, ind: u16) -> Option<NdbStr<'a>> {
+ pub fn get(&self, ind: u16) -> Option<NdbStr<'n>> {
if ind >= self.count() {
return None;
}
Some(self.get_unchecked(ind))
}
- pub fn note(&self) -> &Note<'a> {
- &self.note
+ pub fn note(&'n self) -> &'n Note<'n> {
+ self.note
}
pub fn as_ptr(&self) -> *mut bindings::ndb_tag {
@@ -54,20 +54,20 @@ impl<'a> IntoIterator for Tag<'a> {
#[derive(Debug, Clone)]
pub struct Tags<'a> {
ptr: *mut bindings::ndb_tags,
- note: Note<'a>,
+ note: &'a Note<'a>,
}
impl<'a> IntoIterator for Tags<'a> {
type Item = Tag<'a>;
type IntoIter = TagsIter<'a>;
- fn into_iter(self) -> Self::IntoIter {
- TagsIter::new(self.note().clone())
+ fn into_iter(self) -> TagsIter<'a> {
+ TagsIter::new(self.note())
}
}
impl<'a> Tags<'a> {
- pub(crate) fn new(ptr: *mut bindings::ndb_tags, note: Note<'a>) -> Self {
+ pub(crate) fn new(ptr: *mut bindings::ndb_tags, note: &'a Note<'a>) -> Self {
Tags { ptr, note }
}
@@ -76,11 +76,11 @@ impl<'a> Tags<'a> {
}
pub fn iter(&self) -> TagsIter<'a> {
- TagsIter::new(self.note.clone())
+ TagsIter::new(self.note)
}
- pub fn note(&self) -> &Note<'a> {
- &self.note
+ pub fn note(&self) -> &'a Note<'a> {
+ self.note
}
pub fn as_ptr(&self) -> *mut bindings::ndb_tags {
@@ -91,20 +91,17 @@ impl<'a> Tags<'a> {
#[derive(Debug, Clone)]
pub struct TagsIter<'a> {
iter: bindings::ndb_iterator,
- note: Note<'a>,
+ note: &'a Note<'a>,
}
impl<'a> TagsIter<'a> {
- pub fn new(note: Note<'a>) -> Self {
+ pub fn new(note: &'a Note<'a>) -> Self {
let iter = bindings::ndb_iterator {
note: std::ptr::null_mut(),
tag: std::ptr::null_mut(),
index: 0,
};
- let mut iter = TagsIter {
- note: note.clone(),
- iter,
- };
+ let mut iter = TagsIter { note, iter };
unsafe {
bindings::ndb_tags_iterate_start(note.as_ptr(), &mut iter.iter);
};
@@ -116,12 +113,12 @@ impl<'a> TagsIter<'a> {
if tag_ptr.is_null() {
None
} else {
- Some(Tag::new(tag_ptr, self.note().clone()))
+ Some(Tag::new(tag_ptr, self.note()))
}
}
- pub fn note(&self) -> &Note<'a> {
- &self.note
+ pub fn note(&self) -> &'a Note<'a> {
+ self.note
}
pub fn as_ptr(&self) -> *const bindings::ndb_iterator {
@@ -153,7 +150,7 @@ impl<'a> TagIter<'a> {
impl<'a> Iterator for TagIter<'a> {
type Item = NdbStr<'a>;
- fn next(&mut self) -> Option<Self::Item> {
+ fn next(&mut self) -> Option<NdbStr<'a>> {
let tag = self.tag.get(self.index);
if tag.is_some() {
self.index += 1;
diff --git a/src/util/nip10.rs b/src/util/nip10.rs
@@ -242,6 +242,7 @@ pub fn tag_to_noteid_ref(tag: Tag<'_>, index: u16) -> Result<NoteIdRef<'_>, Erro
.get(2)
.and_then(|t| t.variant().str())
.filter(|x| !x.is_empty());
+
let marker = tag
.get(3)
.and_then(|t| t.variant().str())