commit 28a06af534fc46a9b0928c23d25430323865155d parent 208b3331cab907f261a58c0c63911d6cf0bb89c2 Author: William Casarin <jb55@jb55.com> Date: Thu, 1 Feb 2024 17:26:57 -0800 Switch over to use use blocks from nostrdb This is still kind of broken until queries are switched over to nostrdb. Will do this next Signed-off-by: William Casarin <jb55@jb55.com> Diffstat:
51 files changed, 1086 insertions(+), 501 deletions(-)
diff --git a/DamusNotificationService/NotificationFormatter.swift b/DamusNotificationService/NotificationFormatter.swift @@ -103,7 +103,7 @@ struct NotificationFormatter { content.title = Self.zap_notification_title(zap) content.body = Self.zap_notification_body(profiles: state.profiles, zap: zap) content.sound = UNNotificationSound.default - content.userInfo = LossyLocalNotification(type: .zap, mention: .note(notify.event.id)).to_user_info() + content.userInfo = LossyLocalNotification(type: .zap, mention: .init(nip19: .note(notify.event.id))).to_user_info() return (content, "myZapNotification") default: // The sync method should have taken care of this. diff --git a/TODO b/TODO @@ -1 +1,2 @@ Fix q tags +1.5-24 profile loading was much better diff --git a/damus-c/wasm.c b/damus-c/wasm.c @@ -1179,7 +1179,7 @@ static INLINE int parse_i64(struct cursor *read, uint64_t *val) shift = 0; do { - if (!pull_byte(read, &byte)) + if (!cursor_pull_byte(read, &byte)) return 0; *val |= (byte & 0x7FULL) << shift; shift += 7; @@ -1199,7 +1199,7 @@ static INLINE int uleb128_read(struct cursor *read, unsigned int *val) *val = 0; for (;;) { - if (!pull_byte(read, &byte)) + if (!cursor_pull_byte(read, &byte)) return 0; *val |= (0x7F & byte) << shift; @@ -1222,7 +1222,7 @@ static INLINE int sleb128_read(struct cursor *read, signed int *val) shift = 0; do { - if (!pull_byte(read, &byte)) + if (!cursor_pull_byte(read, &byte)) return 0; *val |= ((byte & 0x7F) << shift); shift += 7; @@ -1241,21 +1241,21 @@ static INLINE int uleb128_read(struct cursor *read, unsigned int *val) unsigned char p[6] = {0}; *val = 0; - if (pull_byte(read, &p[0]) && (p[0] & 0x80) == 0) { + if (cursor_pull_byte(read, &p[0]) && (p[0] & 0x80) == 0) { *val = LEB128_1(unsigned int); if (p[0] == 0x7F) assert((int)*val == -1); return 1; - } else if (pull_byte(read, &p[1]) && (p[1] & 0x80) == 0) { + } else if (cursor_pull_byte(read, &p[1]) && (p[1] & 0x80) == 0) { *val = LEB128_2(unsigned int); return 2; - } else if (pull_byte(read, &p[2]) && (p[2] & 0x80) == 0) { + } else if (cursor_pull_byte(read, &p[2]) && (p[2] & 0x80) == 0) { *val = LEB128_3(unsigned int); return 3; - } else if (pull_byte(read, &p[3]) && (p[3] & 0x80) == 0) { + } else if (cursor_pull_byte(read, &p[3]) && (p[3] & 0x80) == 0) { *val = LEB128_4(unsigned int); return 4; - } else if (pull_byte(read, &p[4]) && (p[4] & 0x80) == 0) { + } else if (cursor_pull_byte(read, &p[4]) && (p[4] & 0x80) == 0) { if (!(p[4] & 0xF0)) { *val = LEB128_5(unsigned int); return 5; @@ -1296,7 +1296,7 @@ static int parse_section_tag(struct cursor *cur, enum section_tag *section) start = cur->p; - if (!pull_byte(cur, &byte)) { + if (!cursor_pull_byte(cur, &byte)) { return 0; } @@ -1315,7 +1315,7 @@ static int parse_valtype(struct wasm_parser *p, enum valtype *valtype) start = p->cur.p; - if (unlikely(!pull_byte(&p->cur, (unsigned char*)valtype))) { + if (unlikely(!cursor_pull_byte(&p->cur, (unsigned char*)valtype))) { return parse_err(p, "valtype tag oob"); } @@ -1416,7 +1416,7 @@ static int parse_export_desc(struct wasm_parser *p, enum exportdesc *desc) { unsigned char byte; - if (!pull_byte(&p->cur, &byte)) { + if (!cursor_pull_byte(&p->cur, &byte)) { parse_err(p, "export desc byte eof"); return 0; } @@ -1523,7 +1523,7 @@ static int parse_name_subsection(struct wasm_parser *p, struct namesec *sec, u32 u8 tag; u8 *start = p->cur.p; - if (!pull_byte(&p->cur, &tag)) + if (!cursor_pull_byte(&p->cur, &tag)) return parse_err(p, "name subsection tag oob?"); if (!is_valid_name_subsection(tag)) @@ -1676,7 +1676,7 @@ static int parse_reftype(struct wasm_parser *p, enum reftype *reftype) { u8 tag; - if (!pull_byte(&p->cur, &tag)) { + if (!cursor_pull_byte(&p->cur, &tag)) { parse_err(p, "reftype"); return 0; } @@ -1720,7 +1720,7 @@ static int parse_export_section(struct wasm_parser *p, static int parse_limits(struct wasm_parser *p, struct limits *limits) { unsigned char tag; - if (!pull_byte(&p->cur, &tag)) { + if (!cursor_pull_byte(&p->cur, &tag)) { return parse_err(p, "oob"); } @@ -1803,7 +1803,7 @@ static void print_code(u8 *code, int code_len) make_cursor(code, code + code_len, &c); for (;;) { - if (!pull_byte(&c, &tag)) { + if (!cursor_pull_byte(&c, &tag)) { break; } @@ -2169,7 +2169,7 @@ static int parse_const_expr(struct expr_parser *p, struct expr *expr) expr->code = p->code->p; while (1) { - if (unlikely(!pull_byte(p->code, &tag))) { + if (unlikely(!cursor_pull_byte(p->code, &tag))) { return note_error(p->errs, p->code, "oob"); } @@ -2332,7 +2332,7 @@ static int parse_instrs_until_at(struct expr_parser *p, u8 stop_instr, p->code->p - p->code->start, dbg_inst, instr_name(stop_instr)); for (;;) { - if (!pull_byte(p->code, &tag)) + if (!cursor_pull_byte(p->code, &tag)) return note_error(p->errs, p->code, "oob"); if ((tag != i_if && tag == stop_instr) || @@ -2413,7 +2413,7 @@ static int parse_element(struct wasm_parser *p, struct elem *elem) make_expr_parser(&p->errs, &p->cur, &expr_parser); - if (!pull_byte(&p->cur, &tag)) + if (!cursor_pull_byte(&p->cur, &tag)) return parse_err(p, "tag"); if (tag > 7) @@ -2545,7 +2545,7 @@ static int parse_wdata(struct wasm_parser *p, struct wdata *data) struct expr_parser parser; u8 tag; - if (!pull_byte(&p->cur, &tag)) { + if (!cursor_pull_byte(&p->cur, &tag)) { return parse_err(p, "tag"); } @@ -2700,7 +2700,7 @@ static int parse_importdesc(struct wasm_parser *p, struct importdesc *desc) { u8 tag; - if (!pull_byte(&p->cur, &tag)) { + if (!cursor_pull_byte(&p->cur, &tag)) { parse_err(p, "oom"); return 0; } @@ -4134,7 +4134,7 @@ static int parse_blocktype(struct cursor *cur, struct errors *errs, struct block { unsigned char byte; - if (unlikely(!pull_byte(cur, &byte))) { + if (unlikely(!cursor_pull_byte(cur, &byte))) { return note_error(errs, cur, "parse_blocktype: oob\n"); } @@ -4656,7 +4656,7 @@ static int parse_bulk_op(struct cursor *code, struct errors *errs, { u8 tag; - if (unlikely(!pull_byte(code, &tag))) + if (unlikely(!cursor_pull_byte(code, &tag))) return note_error(errs, code, "oob"); if (unlikely(tag < 10 || tag > 17)) @@ -6552,7 +6552,7 @@ static INLINE int interp_parse_instr(struct wasm_interp *interp, { u8 tag; - if (unlikely(!pull_byte(code, &tag))) { + if (unlikely(!cursor_pull_byte(code, &tag))) { return interp_error(interp, "no more instrs to pull"); } diff --git a/damus-c/wasm.h b/damus-c/wasm.h @@ -27,6 +27,8 @@ static const unsigned char WASM_MAGIC[] = {0,'a','s','m'}; #define interp_error(p, fmt, ...) note_error(&((p)->errors), interp_codeptr(p), fmt, ##__VA_ARGS__) #define parse_err(p, fmt, ...) note_error(&((p)->errs), &(p)->cur, fmt, ##__VA_ARGS__) +#include "short_types.h" + enum valtype { val_i32 = 0x7F, val_i64 = 0x7E, diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -309,6 +309,28 @@ 4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; }; 4CB8FC232A41ABA800763C51 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8FC222A41ABA500763C51 /* AboutView.swift */; }; 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; }; + 4CBB6F662B72B5DD000477A4 /* NdbBlocksIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480562B633F2600F2B2C0 /* NdbBlocksIterator.swift */; }; + 4CBB6F672B72B5E8000477A4 /* NdbBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480582B633F3800F2B2C0 /* NdbBlock.swift */; }; + 4CBB6F682B72B5F0000477A4 /* NdbProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF47FFE2B631C0100F2B2C0 /* NdbProfile.swift */; }; + 4CBB6F692B72C783000477A4 /* NdbBlocksIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480562B633F2600F2B2C0 /* NdbBlocksIterator.swift */; }; + 4CBB6F6A2B730EF1000477A4 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF47FDE2B631C0100F2B2C0 /* nostrdb.c */; }; + 4CBB6F6B2B7310EC000477A4 /* tal.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF4801F2B631C0100F2B2C0 /* tal.c */; }; + 4CBB6F6C2B7310F8000477A4 /* sha256.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF47FF52B631C0100F2B2C0 /* sha256.c */; }; + 4CBB6F6D2B731102000477A4 /* take.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480232B631C0100F2B2C0 /* take.c */; }; + 4CBB6F6E2B731113000477A4 /* block.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF47FDF2B631C0100F2B2C0 /* block.c */; }; + 4CBB6F6F2B73116B000477A4 /* content_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF47FF62B631C0100F2B2C0 /* content_parser.c */; }; + 4CBB6F702B731179000477A4 /* invoice.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480372B631C0100F2B2C0 /* invoice.c */; }; + 4CBB6F712B731184000477A4 /* bolt11.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480102B631C0100F2B2C0 /* bolt11.c */; }; + 4CBB6F722B7311AA000477A4 /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480122B631C0100F2B2C0 /* list.c */; }; + 4CBB6F732B7311AA000477A4 /* mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480182B631C0100F2B2C0 /* mem.c */; }; + 4CBB6F742B7311AA000477A4 /* hash_u5.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF4801A2B631C0100F2B2C0 /* hash_u5.c */; }; + 4CBB6F752B7311AA000477A4 /* talstr.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF4801C2B631C0100F2B2C0 /* talstr.c */; }; + 4CBB6F762B7311AA000477A4 /* utf8.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF4801D2B631C0100F2B2C0 /* utf8.c */; }; + 4CBB6F772B7311AA000477A4 /* bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF4801E2B631C0100F2B2C0 /* bech32.c */; }; + 4CBB6F782B7311AA000477A4 /* amount.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480252B631C0100F2B2C0 /* amount.c */; }; + 4CBB6F792B7311AA000477A4 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480262B631C0100F2B2C0 /* error.c */; }; + 4CBB6F7A2B7311AA000477A4 /* bech32_util.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480282B631C0100F2B2C0 /* bech32_util.c */; }; + 4CBB6F7C2B7312A7000477A4 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF47FE52B631C0100F2B2C0 /* nostr_bech32.c */; }; 4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; }; 4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FEE2A73FCCB007AEB17 /* IdType.swift */; }; 4CC14FF12A73FCDB007AEB17 /* Pubkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF02A73FCDB007AEB17 /* Pubkey.swift */; }; @@ -398,10 +420,9 @@ 4CF4804D2B631C0100F2B2C0 /* amount.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480252B631C0100F2B2C0 /* amount.c */; }; 4CF4804E2B631C0100F2B2C0 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480262B631C0100F2B2C0 /* error.c */; }; 4CF4804F2B631C0100F2B2C0 /* bech32_util.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480282B631C0100F2B2C0 /* bech32_util.c */; }; - 4CF480502B631C0100F2B2C0 /* libnostrdb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CF4802A2B631C0100F2B2C0 /* libnostrdb.a */; }; - 4CF480512B631C0100F2B2C0 /* node_id.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480342B631C0100F2B2C0 /* node_id.c */; }; 4CF480522B631C0100F2B2C0 /* invoice.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480372B631C0100F2B2C0 /* invoice.c */; }; 4CF480552B631C4F00F2B2C0 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480532B631C4F00F2B2C0 /* wasm.c */; }; + 4CF480592B633F3800F2B2C0 /* NdbBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF480582B633F3800F2B2C0 /* NdbBlock.swift */; }; 4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD502E2A2DA45800A229DB /* MediaView.swift */; }; 4CFF8F5929C9FD1E008DB934 /* DamusPurpleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */; }; 4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; }; @@ -1982,6 +2003,7 @@ 4C1253652A76D0FF0004F4B8 /* OnlyZapsNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlyZapsNotify.swift; sourceTree = "<group>"; }; 4C1253672A76D2470004F4B8 /* MuteNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteNotify.swift; sourceTree = "<group>"; }; 4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaysChangedNotify.swift; sourceTree = "<group>"; }; + 4C15224A2B8D499F007CDC17 /* parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = "<group>"; }; 4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsSettingsView.swift; sourceTree = "<group>"; }; 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; }; 4C190F242A547D2000027FD5 /* LoadScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadScript.swift; sourceTree = "<group>"; }; @@ -2437,6 +2459,8 @@ 4CF480372B631C0100F2B2C0 /* invoice.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = invoice.c; sourceTree = "<group>"; }; 4CF480532B631C4F00F2B2C0 /* wasm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wasm.c; sourceTree = "<group>"; }; 4CF480542B631C4F00F2B2C0 /* wasm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wasm.h; sourceTree = "<group>"; }; + 4CF480562B633F2600F2B2C0 /* NdbBlocksIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbBlocksIterator.swift; sourceTree = "<group>"; }; + 4CF480582B633F3800F2B2C0 /* NdbBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbBlock.swift; sourceTree = "<group>"; }; 4CFD502E2A2DA45800A229DB /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; }; 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleView.swift; sourceTree = "<group>"; }; 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; }; @@ -2687,7 +2711,6 @@ D7C48C0B2D12DE0C00A3BACF /* SwiftyCrop in Frameworks */, D78DB8592C1CE9CA00F0AB12 /* SwipeActions in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, - 4CF480502B631C0100F2B2C0 /* libnostrdb.a in Frameworks */, 4C27C9322A64766F007DBC75 /* MarkdownUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3144,6 +3167,7 @@ isa = PBXGroup; children = ( D733F9E42D92C75C00317B11 /* UnownedNdbNote.swift */, + 4C15224A2B8D499F007CDC17 /* parser.h */, 4CF47FDC2B631C0100F2B2C0 /* src */, 4C47928D2A9939BD00489948 /* flatcc */, 4CE9FBBB2A6B3D9C007E485C /* Test */, @@ -3163,6 +3187,8 @@ 4C78EFDA2A707C67007E8197 /* secp256k1_extrakeys.h */, 4C78EFD92A707C4D007E8197 /* secp256k1.h */, D798D2272B085CDA00234419 /* NdbNote+.swift */, + 4CF480562B633F2600F2B2C0 /* NdbBlocksIterator.swift */, + 4CF480582B633F3800F2B2C0 /* NdbBlock.swift */, ); path = nostrdb; sourceTree = "<group>"; @@ -5167,6 +5193,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4CBB6F692B72C783000477A4 /* NdbBlocksIterator.swift in Sources */, 4C3DCC762A9FE9EC0091E592 /* NdbTxn.swift in Sources */, 4CEF958D2A9CE650000F901B /* verifier.c in Sources */, D73BDB0E2D6FF5F600D69970 /* NostrNetworkManager.swift in Sources */, @@ -5312,7 +5339,6 @@ D74AAFC22B153395006CF0F4 /* HeadlessDamusState.swift in Sources */, 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */, D73BDB1A2D71311900D69970 /* UserRelayListErrors.swift in Sources */, - 4CF480512B631C0100F2B2C0 /* node_id.c in Sources */, 4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */, 4C86F7C42A76C44C00EC0817 /* ZappingNotify.swift in Sources */, 4C363A8428233689006E126D /* Parser.swift in Sources */, @@ -5354,6 +5380,7 @@ D733F9E32D92C1D900317B11 /* SubscriptionManager.swift in Sources */, F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */, 4C285C8228385570008A31F1 /* CarouselView.swift in Sources */, + 4CF480592B633F3800F2B2C0 /* NdbBlock.swift in Sources */, 3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */, F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */, 4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */, @@ -5492,7 +5519,6 @@ 4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */, 4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */, 50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */, - 4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */, 5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */, 4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */, 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */, @@ -6795,6 +6821,27 @@ files = ( 4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */, D733F9E52D92C76100317B11 /* UnownedNdbNote.swift in Sources */, + 4CBB6F7C2B7312A7000477A4 /* nostr_bech32.c in Sources */, + 4CBB6F722B7311AA000477A4 /* list.c in Sources */, + 4CBB6F732B7311AA000477A4 /* mem.c in Sources */, + 4CBB6F742B7311AA000477A4 /* hash_u5.c in Sources */, + 4CBB6F752B7311AA000477A4 /* talstr.c in Sources */, + 4CBB6F762B7311AA000477A4 /* utf8.c in Sources */, + 4CBB6F772B7311AA000477A4 /* bech32.c in Sources */, + 4CBB6F782B7311AA000477A4 /* amount.c in Sources */, + 4CBB6F792B7311AA000477A4 /* error.c in Sources */, + 4CBB6F7A2B7311AA000477A4 /* bech32_util.c in Sources */, + 4CBB6F712B731184000477A4 /* bolt11.c in Sources */, + 4CBB6F702B731179000477A4 /* invoice.c in Sources */, + 4CBB6F6F2B73116B000477A4 /* content_parser.c in Sources */, + 4CBB6F6E2B731113000477A4 /* block.c in Sources */, + 4CBB6F6D2B731102000477A4 /* take.c in Sources */, + 4CBB6F6C2B7310F8000477A4 /* sha256.c in Sources */, + 4CBB6F6B2B7310EC000477A4 /* tal.c in Sources */, + 4CBB6F6A2B730EF1000477A4 /* nostrdb.c in Sources */, + 4CBB6F682B72B5F0000477A4 /* NdbProfile.swift in Sources */, + 4CBB6F672B72B5E8000477A4 /* NdbBlock.swift in Sources */, + 4CBB6F662B72B5DD000477A4 /* NdbBlocksIterator.swift in Sources */, D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */, D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */, D71AD9002CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */, @@ -6909,7 +6956,6 @@ D7EDED262B117FC80018B19C /* StringUtil.swift in Sources */, D7CE1B1E2B0BE190002EDAD4 /* midl.c in Sources */, D7CB5D3C2B1130C600AD4105 /* LocalNotification.swift in Sources */, - D7CE1B2D2B0BE250002EDAD4 /* take.c in Sources */, B59CAD4D2B688D1000677E8B /* MutelistManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -1220,8 +1220,8 @@ extension LossyLocalNotification { /// Computes a view open action from a mention reference. /// Use this when opening a user-presentable interface to a specific mention reference. func toViewOpenAction() -> ContentView.ViewOpenAction { - switch self.mention { - case .pubkey(let pubkey): + switch self.mention.nip19 { + case .npub(let pubkey): return .route(.ProfileByKey(pubkey: pubkey)) case .note(let noteId): return .route(.LoadableNostrEvent(note_reference: .note_id(noteId))) @@ -1241,6 +1241,15 @@ extension LossyLocalNotification { ))) case .naddr(let nAddr): return .route(.LoadableNostrEvent(note_reference: .naddr(nAddr))) + case .nsec(_): + // `nsec` urls are a terrible idea security-wise, so we should intentionally not support those — in order to discourage their use. + return .sheet(.error(ErrorView.UserPresentableError( + user_visible_description: NSLocalizedString("You opened an invalid link. The link you tried to open refers to \"nsec\", which is not supported.", comment: "User-visible error description for a user who tries to open an unsupported \"nsec\" link."), + tip: NSLocalizedString("Please contact the person who provided the link, and ask for another link. Also, this link may have sensitive information, please use caution before sharing it.", comment: "User-visible tip on what to do if a link contains an unsupported \"nsec\" reference."), + technical_info: "`MentionRef.toViewOpenAction` detected unsupported `nsec` contents" + ))) + case .nscript(let script): + return .route(.Script(script: ScriptModel(data: script, state: .not_loaded))) } } } diff --git a/damus/Core/Nostr/Mentions.swift b/damus/Core/Nostr/Mentions.swift @@ -18,22 +18,38 @@ enum MentionType: AsciiCharacter, TagKey { } } -enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable { - case pubkey(Pubkey) - case note(NoteId) - case nevent(NEvent) - case nprofile(NProfile) - case nrelay(String) - case naddr(NAddr) +extension UnsafePointer<UInt8> { + func as_data(size: Int) -> Data { + return Data(bytes: self, count: size) + } +} + +struct MentionRef: TagKeys, TagConvertible, Equatable, Hashable { + let nip19: Bech32Object + + static func note(_ note_id: NoteId) -> MentionRef { + return self.init(nip19: .note(note_id)) + } + + init?(block: ndb_mention_bech32_block) { + guard let bech32_obj = Bech32Object.init(block: block) else { + return nil + } + self.nip19 = bech32_obj + } + + init(nip19: Bech32Object) { + self.nip19 = nip19 + } var key: MentionType { - switch self { - case .pubkey: return .p - case .note: return .e - case .nevent: return .e - case .nprofile: return .p + switch self.nip19 { + case .note, .nevent: return .e + case .nprofile, .npub: return .p case .nrelay: return .r case .naddr: return .a + case .nscript: return .a + case .nsec: return .p } } @@ -41,38 +57,40 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable { return Bech32Object.encode(toBech32Object()) } - static func from_bech32(str: String) -> MentionRef? { - switch Bech32Object.parse(str) { - case .note(let noteid): return .note(noteid) - case .npub(let pubkey): return .pubkey(pubkey) - default: return nil + init?(bech32_str: String) { + guard let obj = Bech32Object.parse(bech32_str) else { + return nil } + + self.nip19 = obj } var pubkey: Pubkey? { - switch self { - case .pubkey(let pubkey): return pubkey + switch self.nip19 { + case .npub(let pubkey): return pubkey case .note: return nil case .nevent(let nevent): return nevent.author case .nprofile(let nprofile): return nprofile.author case .nrelay: return nil case .naddr: return nil + case .nsec(let prv): return privkey_to_pubkey(privkey: prv) + case .nscript(_): return nil } } var tag: [String] { - switch self { - case .pubkey(let pubkey): return ["p", pubkey.hex()] + switch self.nip19 { + case .npub(let pubkey): return ["p", pubkey.hex()] case .note(let noteId): return ["e", noteId.hex()] case .nevent(let nevent): var tagBuilder = ["e", nevent.noteid.hex()] let relay = nevent.relays.first if let author = nevent.author?.hex() { - tagBuilder.append(relay ?? "") + tagBuilder.append(relay?.absoluteString ?? "") tagBuilder.append(author) } else if let relay { - tagBuilder.append(relay) + tagBuilder.append(relay.absoluteString) } return tagBuilder @@ -80,7 +98,7 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable { var tagBuilder = ["p", nprofile.author.hex()] if let relay = nprofile.relays.first { - tagBuilder.append(relay) + tagBuilder.append(relay.absoluteString) } return tagBuilder @@ -89,10 +107,14 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable { var tagBuilder = ["a", "\(naddr.kind.description):\(naddr.author.hex()):\(naddr.identifier.string())"] if let relay = naddr.relays.first { - tagBuilder.append(relay) + tagBuilder.append(relay.absoluteString) } return tagBuilder + case .nsec(_): + return [] + case .nscript(_): + return [] } } @@ -112,10 +134,10 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable { switch mention_type { case .p: guard let data = element.id() else { return nil } - return .pubkey(Pubkey(data)) + return .init(nip19: .npub(Pubkey(data))) case .e: guard let data = element.id() else { return nil } - return .note(NoteId(data)) + return .init(nip19: .note(NoteId(data))) case .a: let str = element.string() let data = str.split(separator: ":") @@ -124,26 +146,13 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable { guard let pubkey = Pubkey(hex: String(data[1])) else { return nil } guard let kind = UInt32(data[0]) else { return nil } - return .naddr(NAddr(identifier: String(data[2]), author: pubkey, relays: [], kind: kind)) - case .r: return .nrelay(element.string()) + return .init(nip19: .naddr(NAddr(identifier: String(data[2]), author: pubkey, relays: [], kind: kind))) + case .r: return .init(nip19: .nrelay(element.string())) } } func toBech32Object() -> Bech32Object { - switch self { - case .pubkey(let pk): - return .npub(pk) - case .note(let noteid): - return .note(noteid) - case .naddr(let naddr): - return .naddr(naddr) - case .nevent(let nevent): - return .nevent(nevent) - case .nprofile(let nprofile): - return .nprofile(nprofile) - case .nrelay(let url): - return .nrelay(url) - } + self.nip19 } } @@ -185,7 +194,6 @@ struct LightningInvoice<T> { let amount: T let string: String let expiry: UInt64 - let payment_hash: Data let created_at: UInt64 var abbreviated: String { @@ -213,8 +221,8 @@ struct LightningInvoice<T> { } } -func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? { - guard p != nil else { +func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>?) -> T? { + guard let p else { return nil } return p.pointee @@ -282,7 +290,7 @@ func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String { return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats) } -func convert_invoice_description(b11: bolt11) -> InvoiceDescription? { +func convert_invoice_description(b11: ndb_invoice) -> InvoiceDescription? { if let desc = b11.description { return .description(String(cString: desc)) } @@ -307,3 +315,49 @@ func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? { return nil } + +struct PostTags { + let blocks: [Block] + let tags: [[String]] +} + +/// Convert +func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { + var new_tags = tags + + for post_block in post_blocks { + switch post_block { + case .mention(let mention): + switch(mention.ref.nip19) { + case .note, .nevent: + continue + default: + break + } + + new_tags.append(mention.ref.tag) + case .hashtag(let hashtag): + new_tags.append(["t", hashtag.lowercased()]) + case .text: break + case .invoice: break + case .relay: break + case .url(let url): + new_tags.append(["r", url.absoluteString]) + break + } + } + + return PostTags(blocks: post_blocks, tags: new_tags) +} + +func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? { + let tags = post.references.map({ r in r.tag }) + post.tags + guard let post_blocks = parse_post_blocks(content: post.content)?.blocks else { + return nil + } + let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags) + let content = post_tags.blocks + .map({ b in b.asString }) + .joined(separator: "") + return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags) +} diff --git a/damus/Core/Nostr/NostrEvent.swift b/damus/Core/Nostr/NostrEvent.swift @@ -778,13 +778,18 @@ func validate_event(ev: NostrEvent) -> ValidationResult { return ok ? .ok : .bad_sig } -func first_eref_mention(ev: NostrEvent, keypair: Keypair) -> Mention<NoteId>? { - let blocks = ev.blocks(keypair).blocks.filter { block in +func first_eref_mention(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> Mention<NoteId>? { + guard let blocks_txn = ev.blocks(ndb: ndb) else { + return nil + } + + let ndb_blocks = blocks_txn.unsafeUnownedValue + let blocks = ndb_blocks.iter(note: ev).filter { block in guard case .mention(let mention) = block else { return false } - switch mention.ref { + switch mention.bech32_type { case .note, .nevent: return true default: @@ -795,11 +800,13 @@ func first_eref_mention(ev: NostrEvent, keypair: Keypair) -> Mention<NoteId>? { /// MARK: - Preview if let firstBlock = blocks.first, case .mention(let mention) = firstBlock { - switch mention.ref { - case .note(let note_id): - return .note(note_id) - case .nevent(let nevent): - return .note(nevent.noteid) + switch mention.bech32_type { + case .note: + let data = mention.bech32.note.event_id.as_data(size: 32) + return .note(NoteId(data)) + case .nevent: + let data = mention.bech32.nevent.event_id.as_data(size: 32) + return .note(NoteId(data)) default: return nil } @@ -807,9 +814,15 @@ func first_eref_mention(ev: NostrEvent, keypair: Keypair) -> Mention<NoteId>? { return nil } -func separate_invoices(ev: NostrEvent, keypair: Keypair) -> [Invoice]? { - let invoiceBlocks: [Invoice] = ev.blocks(keypair).blocks.reduce(into: []) { invoices, block in - guard case .invoice(let invoice) = block else { +func separate_invoices(ndb: Ndb, ev: NostrEvent) -> [Invoice]? { + guard let blocks_txn = ev.blocks(ndb: ndb) else { + return nil + } + let ndb_blocks = blocks_txn.unsafeUnownedValue + let invoiceBlocks: [Invoice] = ndb_blocks.iter(note: ev).reduce(into: []) { invoices, block in + guard case .invoice(let invoice) = block, + let invoice = invoice.as_invoice() + else { return } invoices.append(invoice) diff --git a/damus/Core/Nostr/NostrResponse.swift b/damus/Core/Nostr/NostrResponse.swift @@ -89,7 +89,7 @@ enum NostrResponse { free(data) return nil } - let new_note = note_data.assumingMemoryBound(to: ndb_note.self) + let new_note = ndb_note_ptr(ptr: OpaquePointer(note_data)) let note = NdbNote(note: new_note, size: Int(len), owned: true, key: nil) guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else { diff --git a/damus/Core/Types/Block.swift b/damus/Core/Types/Block.swift @@ -2,22 +2,11 @@ // Block.swift // damus // -// Created by Kyle Roucis on 2023-08-21. -// import Foundation -fileprivate extension String { - /// Failable initializer to build a Swift.String from a C-backed `str_block_t`. - init?(_ s: str_block_t) { - let len = s.end - s.start - let bytes = Data(bytes: s.start, count: len) - self.init(bytes: bytes, encoding: .utf8) - } -} - -/// Represents a block of data stored by the NOSTR protocol. This can be +/// Represents a block of data stored in nostrdb. This can be /// simple text, a hashtag, a url, a relay reference, a mention ref and /// potentially more in the future. enum Block: Equatable { @@ -38,22 +27,6 @@ enum Block: Equatable { } } - var is_previewable: Bool { - switch self { - case .mention(let m): - switch m.ref { - case .note, .nevent: return true - default: return false - } - case .invoice: - return true - case .url: - return true - default: - return false - } - } - case text(String) case mention(Mention<MentionRef>) case hashtag(String) @@ -67,61 +40,56 @@ struct Blocks: Equatable { let blocks: [Block] } +extension ndb_str_block { + func as_str() -> String { + let buf = UnsafeBufferPointer(start: self.str, count: Int(self.len)) + let uint8Buf = buf.map { UInt8(bitPattern: $0) } + return String(decoding: uint8Buf, as: UTF8.self) + } +} + +extension ndb_block_ptr { + func as_str() -> String { + guard let str_block = ndb_block_str(self.ptr) else { + return "" + } + return str_block.pointee.as_str() + } + + var block: ndb_block.__Unnamed_union_block { + self.ptr.pointee.block + } +} + extension Block { /// Failable initializer for the C-backed type `block_t`. This initializer will inspect /// the underlying block type and build the appropriate enum value as needed. - init?(_ block: block_t, tags: TagsSequence? = nil) { - switch block.type { + init?(block: ndb_block_ptr, tags: TagsSequence?) { + switch ndb_get_block_type(block.ptr) { case BLOCK_HASHTAG: - guard let str = String(block.block.str) else { - return nil - } - self = .hashtag(str) + self = .hashtag(block.as_str()) case BLOCK_TEXT: - guard let str = String(block.block.str) else { - return nil - } - self = .text(str) + self = .text(block.as_str()) case BLOCK_MENTION_INDEX: guard let b = Block(index: Int(block.block.mention_index), tags: tags) else { return nil } self = b case BLOCK_URL: - guard let b = Block(block.block.str) else { - return nil - } - self = b + guard let url = URL(string: block.as_str()) else { return nil } + self = .url(url) case BLOCK_INVOICE: - guard let b = Block(invoice: block.block.invoice) else { - return nil - } + guard let b = Block(invoice: block.block.invoice) else { return nil } self = b case BLOCK_MENTION_BECH32: - guard let b = Block(bech32: block.block.mention_bech32) else { - return nil - } + guard let b = Block(bech32: block.block.mention_bech32) else { return nil } self = b default: return nil } } } -fileprivate extension Block { - /// Failable initializer for the C-backed type `str_block_t`. - init?(_ b: str_block_t) { - guard let str = String(b) else { - return nil - } - if let url = URL(string: str) { - self = .url(url) - } - else { - self = .text(str) - } - } -} fileprivate extension Block { /// Failable initializer for a block index and a tag sequence. init?(index: Int, tags: TagsSequence? = nil) { @@ -143,34 +111,19 @@ fileprivate extension Block { } } } + fileprivate extension Block { /// Failable initializer for the C-backed type `invoice_block_t`. - init?(invoice: invoice_block_t) { - guard let invstr = String(invoice.invstr) else { - return nil - } - - guard var b11 = maybe_pointee(invoice.bolt11) else { - return nil - } - - guard let description = convert_invoice_description(b11: b11) else { - return nil - } - - let amount: Amount = maybe_pointee(b11.msat).map { .specific(Int64($0.millisatoshis)) } ?? .any - let payment_hash = Data(bytes: &b11.payment_hash, count: 32) - let created_at = b11.timestamp - - tal_free(invoice.bolt11) - self = .invoice(Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, payment_hash: payment_hash, created_at: created_at)) + init?(invoice: ndb_invoice_block) { + guard let invoice = invoice.as_invoice() else { return nil } + self = .invoice(invoice) } } fileprivate extension Block { /// Failable initializer for the C-backed type `mention_bech32_block_t`. This initializer will inspect the /// bech32 type code and build the appropriate enum type. - init?(bech32 b: mention_bech32_block_t) { + init?(bech32 b: ndb_mention_bech32_block) { guard let decoded = decodeCBech32(b.bech32) else { return nil } @@ -180,6 +133,7 @@ fileprivate extension Block { self = .mention(.any(ref)) } } + extension Block { var asString: String { switch self { diff --git a/damus/Features/DMs/Views/DMChatView.swift b/damus/Features/DMs/Views/DMChatView.swift @@ -126,10 +126,12 @@ struct DMChatView: View, KeyboardReadable { func send_message() { let tags = [["p", pubkey.hex()]] - let post_blocks = parse_post_blocks(content: dms.draft) - let content = post_blocks - .map(\.asString) - .joined(separator: "") + let post_blocks = parse_post_blocks(content: dms.draft)?.blocks + guard let content = post_blocks?.map({ pb in pb.asString }).joined(separator: "") else { + // TODO: handle these errors somehow? + print("error creating dm") + return + } guard let dm = NIP04.create_dm(content, to_pk: pubkey, tags: tags, keypair: damus_state.keypair) else { print("error creating dm") diff --git a/damus/Features/DMs/Views/DMView.swift b/damus/Features/DMs/Views/DMView.swift @@ -17,7 +17,7 @@ struct DMView: View { var Mention: some View { Group { - if let mention = first_eref_mention(ev: event, keypair: damus_state.keypair) { + if let mention = first_eref_mention(ndb: damus_state.ndb, ev: event, keypair: damus_state.keypair) { BuilderEventView(damus: damus_state, event_id: mention.ref) } else { EmptyView() diff --git a/damus/Features/Events/EventShell.swift b/damus/Features/Events/EventShell.swift @@ -35,12 +35,12 @@ struct EventShell<Content: View>: View { !options.contains(.no_action_bar) } - func get_mention() -> Mention<NoteId>? { + func get_mention(ndb: Ndb) -> Mention<NoteId>? { if self.options.contains(.nested) || self.options.contains(.no_mentions) { return nil } - return first_eref_mention(ev: event, keypair: state.keypair) + return first_eref_mention(ndb: ndb, ev: event, keypair: state.keypair) } var ActionBar: some View { @@ -73,7 +73,7 @@ struct EventShell<Content: View>: View { content - if let mention = get_mention() { + if let mention = get_mention(ndb: state.ndb) { MentionView(damus_state: state, mention: mention) } @@ -103,7 +103,9 @@ struct EventShell<Content: View>: View { content - if !options.contains(.no_mentions), let mention = get_mention() { + if !options.contains(.no_mentions), + let mention = get_mention(ndb: state.ndb) + { MentionView(damus_state: state, mention: mention) .padding(.horizontal) } diff --git a/damus/Features/Events/Models/NoteContent.swift b/damus/Features/Events/Models/NoteContent.swift @@ -66,14 +66,16 @@ func note_artifact_is_separated(kind: NostrKind?) -> Bool { return kind != .longform } -func render_note_content(ev: NostrEvent, profiles: Profiles, keypair: Keypair) -> NoteArtifacts { - let blocks = ev.blocks(keypair) +func render_note_content(ndb: Ndb, ev: NostrEvent, profiles: Profiles, keypair: Keypair) -> NoteArtifacts { + guard let blocks = ev.blocks(ndb: ndb) else { + return .separated(.just_content(ev.get_content(keypair))) + } if ev.known_kind == .longform { return .longform(LongformContent(ev.content)) } - return .separated(render_blocks(blocks: blocks, profiles: profiles, can_hide_last_previewable_refs: true)) + return .separated(render_blocks(blocks: blocks.unsafeUnownedValue, profiles: profiles, note: ev, can_hide_last_previewable_refs: true)) } // FIXME(tyiu): There are a lot of hacks to get this function to render the blocks correctly. @@ -81,39 +83,54 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, keypair: Keypair) - // Block previews should actually be rendered in the position of the note content where it was found. // Currently, we put some previews at the bottom of the note, which is incorrect as they take things out of // the author's intended context. -func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated { +func render_blocks(blocks: NdbBlocks, profiles: Profiles, note: NdbNote, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated { var invoices: [Invoice] = [] var urls: [UrlType] = [] - let blocks = bs.blocks - + var end_mention_count = 0 var end_url_count = 0 + + let ndb_blocks = blocks.iter(note: note).collect() + let one_note_ref = ndb_blocks + .filter({ + if case .mention(let mention) = $0, + let typ = mention.bech32_type, + typ.is_notelike { + return true + } + return false + }) + .count == 1 // Search backwards until we find the beginning index of the chain of previewables that reach the end of the content. - var hide_text_index = blocks.endIndex + var hide_text_index = ndb_blocks.endIndex if can_hide_last_previewable_refs { - outerLoop: for (i, block) in blocks.enumerated().reversed() { + outerLoop: for (i, block) in ndb_blocks.enumerated().reversed() { if block.is_previewable { switch block { case .mention: end_mention_count += 1 - + // If there is more than one previewable mention, // do not hide anything because we allow rich rendering of only one mention currently. // This should be fixed in the future to show events inline instead. if end_mention_count > 1 { - hide_text_index = blocks.endIndex + hide_text_index = ndb_blocks.endIndex break outerLoop } - case .url(let url): + case .url(let url_block): + guard let url_string = NdbBlock.convertToStringCopy(from: url_block), + let url = URL(string: url_string) else { + continue // We can't classify this, ignore and move on + } let url_type = classify_url(url) if case .link = url_type { end_url_count += 1 - + // If there is more than one link, do not hide anything because we allow rich rendering of only // one link. if end_url_count > 1 { - hide_text_index = blocks.endIndex + hide_text_index = ndb_blocks.endIndex break outerLoop } } @@ -121,7 +138,9 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewa break } hide_text_index = i - } else if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + } else if case .text(let txt_block) = block, + let txt = NdbBlock.convertToStringCopy(from: txt_block), + txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { // We should hide whitespace at the end sequence. hide_text_index = i } else if case .hashtag = block { @@ -135,16 +154,21 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewa } var ind: Int = -1 - let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in + let txt: CompatibleText = ndb_blocks.reduce(into: CompatibleText()) { str, block in ind = ind + 1 // Add the rendered previewable blocks to their type-specific lists. switch block { - case .invoice(let invoice): - invoices.append(invoice) - case .url(let url): + case .url(let url_block): + guard let url_string = NdbBlock.convertToStringCopy(from: url_block), + let url = URL(string: url_string) else { + break // We can't classify this, ignore and move on + } let url_type = classify_url(url) urls.append(url_type) + case .invoice(let invoice_block): + guard let invoice = invoice_block.as_invoice() else { break } + invoices.append(invoice) default: break } @@ -153,7 +177,7 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewa // If there are previewable blocks that occur before the consecutive sequence of them at the end of the content, // we should not hide the text representation of any previewable block to avoid altering the format of the note. if ind < hide_text_index && block.is_previewable { - hide_text_index = blocks.endIndex + hide_text_index = ndb_blocks.endIndex } // No need to show the text representation of the block if the only previewables are the sequence of them @@ -162,41 +186,56 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewa // The only exception is that if there are hashtags embedded in the end sequence, which is not uncommon, // then we still want to show those hashtags but hide everything else that is previewable in the end sequence. if ind >= hide_text_index { - if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - if case .hashtag = blocks[safe: ind+1] { - return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: -1, txt: txt)) + if case .text(let txt_block) = block, + let txt = NdbBlock.convertToStringCopy(from: txt_block), + txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + if case .hashtag = ndb_blocks[safe: ind+1] { + str = str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: hide_text_index, txt: txt)) } } else if case .hashtag(let htag) = block { - return str + hashtag_str(htag) + str = str + hashtag_str(htag.as_str()) } - return str + return } } switch block { case .mention(let m): - return str + mention_str(m, profiles: profiles) + if let typ = m.bech32_type, typ.is_notelike, one_note_ref { + return + } + guard let mention = MentionRef(block: m) else { return } + str = str + mention_str(.any(mention), profiles: profiles) case .text(let txt): if case .hashtag = blocks[safe: ind+1] { // SPECIAL CASE: // Do not trim whitespaces from suffix if the following block is a hashtag. // This is because of the code further up (see "SPECIAL CASE"). - return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: -1, txt: txt)) + str = str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: -1, txt: txt.as_str())) } else { - return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: hide_text_index, txt: txt)) + str = str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: hide_text_index, txt: txt.as_str())) } - case .relay(let relay): - return str + CompatibleText(stringLiteral: relay) case .hashtag(let htag): - return str + hashtag_str(htag) + str = str + hashtag_str(htag.as_str()) case .invoice(let invoice): - return str + invoice_str(invoice) + guard let inv = invoice.as_invoice() else { return } + invoices.append(inv) case .url(let url): - return str + url_str(url) + guard let url = URL(string: url.as_str()) else { return } + let url_type = classify_url(url) + switch url_type { + case .media: + urls.append(url_type) + case .link(let url): + urls.append(url_type) + str = str + url_str(url) + } + case .mention_index: + return } } - return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices) + return NoteArtifactsSeparated(content: txt, words: blocks.words, urls: urls, invoices: invoices) } func reduce_text_block(ind: Int, hide_text_index: Int, txt: String) -> String { @@ -262,13 +301,17 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText let bech32String = Bech32Object.encode(m.ref.toBech32Object()) let display_str: String = { - switch m.ref { - case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles) + switch m.ref.nip19 { + case .npub(let pk): return getDisplayName(pk: pk, profiles: profiles) case .note: return abbrev_identifier(bech32String) case .nevent: return abbrev_identifier(bech32String) case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles) case .nrelay(let url): return url case .naddr: return abbrev_identifier(bech32String) + case .nsec(let prv): + guard let npub = privkey_to_pubkey(privkey: prv)?.npub else { return "nsec..." } + return abbrev_identifier(npub) + case .nscript(_): return bech32String } }() diff --git a/damus/Features/Events/NoteContentView.swift b/damus/Features/Events/NoteContentView.swift @@ -23,6 +23,21 @@ struct Blur: UIViewRepresentable { } } +extension bech32_nprofile { + func matches_pubkey(pk: Pubkey) -> Bool { + pk.id.withUnsafeBytes { bytes in + memcmp(self.pubkey, bytes, 32) == 0 + } + } +} + +extension bech32_npub { + func matches_pubkey(pk: Pubkey) -> Bool { + pk.id.withUnsafeBytes { bytes in + memcmp(self.pubkey, bytes, 32) == 0 + } + } +} struct NoteContentView: View { @@ -280,7 +295,7 @@ struct NoteContentView: View { } await preload_event(plan: plan, state: damus_state) } else if force_artifacts { - let arts = render_note_content(ev: event, profiles: damus_state.profiles, keypair: damus_state.keypair) + let arts = render_note_content(ndb: damus_state.ndb, ev: event, profiles: damus_state.profiles, keypair: damus_state.keypair) self.artifacts_model.state = .loaded(arts) } } @@ -335,19 +350,36 @@ struct NoteContentView: View { var body: some View { ArtifactContent .onReceive(handle_notify(.profile_updated)) { profile in - let blocks = event.blocks(damus_state.keypair) - for block in blocks.blocks { + guard let blocks_txn = event.blocks(ndb: damus_state.ndb) else { + return + } + let blocks = blocks_txn.unsafeUnownedValue + for block in blocks.iter(note: event) { switch block { case .mention(let m): - if case .pubkey(let pk) = m.ref, pk == profile.pubkey { - load(force_artifacts: true) - return + guard let typ = m.bech32_type else { + continue + } + switch typ { + case .nprofile: + if m.bech32.nprofile.matches_pubkey(pk: profile.pubkey) { + load(force_artifacts: true) + } + case .npub: + if m.bech32.npub.matches_pubkey(pk: profile.pubkey) { + load(force_artifacts: true) + } + case .nevent: continue + case .nrelay: continue + case .nsec: continue + case .note: continue + case .naddr: continue } - case .relay: return case .text: return case .hashtag: return case .url: return case .invoice: return + case .mention_index(_): return } } } @@ -503,13 +535,19 @@ struct NoteContentView_Previews: PreviewProvider { } } -func separate_images(ev: NostrEvent, keypair: Keypair) -> [MediaUrl]? { - let urlBlocks: [URL] = ev.blocks(keypair).blocks.reduce(into: []) { urls, block in - guard case .url(let url) = block else { +func separate_images(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> [MediaUrl]? { + guard let blocks_txn = ev.blocks(ndb: ndb) else { + return nil + } + let blocks = blocks_txn.unsafeUnownedValue + let urlBlocks: [URL] = blocks.iter(note: ev).reduce(into: []) { urls, block in + guard case .url(let url) = block, + let parsed_url = URL(string: url.as_str()) else { return } - if classify_url(url).is_img != nil { - urls.append(url) + + if classify_url(parsed_url).is_img != nil { + urls.append(parsed_url) } } let mediaUrls = urlBlocks.map { MediaUrl.image($0) } diff --git a/damus/Features/Events/SelectedEventView.swift b/damus/Features/Events/SelectedEventView.swift @@ -82,7 +82,7 @@ struct SelectedEventView: View { var Mention: some View { Group { - if let mention = first_eref_mention(ev: event, keypair: damus.keypair) { + if let mention = first_eref_mention(ndb: damus.ndb, ev: event, keypair: damus.keypair) { MentionView(damus_state: damus, mention: mention) .padding(.horizontal) } diff --git a/damus/Features/Longform/Views/LongformView.swift b/damus/Features/Longform/Views/LongformView.swift @@ -48,7 +48,7 @@ let test_longform_event = LongformEvent.parse(from: NostrEvent( struct LongformView_Previews: PreviewProvider { static var previews: some View { let st = test_damus_state - let artifacts = render_note_content(ev: test_longform_event.event, profiles: st.profiles, keypair: Keypair(pubkey: .empty, privkey: nil)) + let artifacts = render_note_content(ndb: st.ndb, ev: test_longform_event.event, profiles: st.profiles, keypair: Keypair(pubkey: .empty, privkey: nil)) let model = NoteArtifactsModel(state: .loaded(artifacts)) ScrollView { diff --git a/damus/Features/Notifications/Models/NotificationsManager.swift b/damus/Features/Notifications/Models/NotificationsManager.swift @@ -18,7 +18,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) return } - guard let local_notification = generate_local_notification_object(from: ev, state: state) else { + guard let local_notification = generate_local_notification_object(ndb: state.ndb, from: ev, state: state) else { return } @@ -65,19 +65,21 @@ func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent return true } -func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamusState) -> LocalNotification? { +func generate_local_notification_object(ndb: Ndb, from ev: NostrEvent, state: HeadlessDamusState) -> LocalNotification? { guard let type = ev.known_kind else { return nil } - if type == .text, state.settings.mention_notification { - let blocks = ev.blocks(state.keypair).blocks - - for case .mention(let mention) in blocks { - guard case .pubkey(let pk) = mention.ref, pk == state.keypair.pubkey else { + if type == .text, + state.settings.mention_notification, + let blocks = ev.blocks(ndb: ndb)?.unsafeUnownedValue + { + for case .mention(let mention) in blocks.iter(note: ev) { + guard case .npub = mention.bech32_type, + (memcmp(state.keypair.pubkey.id.bytes, mention.bech32.npub.pubkey, 32) == 0) else { continue } - let content_preview = render_notification_content_preview(ev: ev, profiles: state.profiles, keypair: state.keypair) + let content_preview = render_notification_content_preview(ndb: ndb, ev: ev, profiles: state.profiles, keypair: state.keypair) return LocalNotification(type: .mention, event: ev, target: .note(ev), content: content_preview) } @@ -101,13 +103,13 @@ func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamu state.settings.repost_notification, let inner_ev = ev.get_inner_event() { - let content_preview = render_notification_content_preview(ev: inner_ev, profiles: state.profiles, keypair: state.keypair) + let content_preview = render_notification_content_preview(ndb: ndb, ev: inner_ev, profiles: state.profiles, keypair: state.keypair) return LocalNotification(type: .repost, event: ev, target: .note(inner_ev), content: content_preview) } else if type == .like, state.settings.like_notification, let evid = ev.referenced_ids.last { if let txn = state.ndb.lookup_note(evid, txn_name: "local_notification_like"), let liked_event = txn.unsafeUnownedValue { - let content_preview = render_notification_content_preview(ev: liked_event, profiles: state.profiles, keypair: state.keypair) + let content_preview = render_notification_content_preview(ndb: ndb, ev: liked_event, profiles: state.profiles, keypair: state.keypair) return LocalNotification(type: .like, event: ev, target: .note(liked_event), content: content_preview) } else { return LocalNotification(type: .like, event: ev, target: .note_id(evid), content: "") @@ -144,10 +146,10 @@ func create_local_notification(profiles: Profiles, notify: LocalNotification) { } } -func render_notification_content_preview(ev: NostrEvent, profiles: Profiles, keypair: Keypair) -> String { +func render_notification_content_preview(ndb: Ndb, ev: NostrEvent, profiles: Profiles, keypair: Keypair) -> String { let prefix_len = 300 - let artifacts = render_note_content(ev: ev, profiles: profiles, keypair: keypair) + let artifacts = render_note_content(ndb: ndb, ev: ev, profiles: profiles, keypair: keypair) // special case for longform events if ev.known_kind == .longform { diff --git a/damus/Features/Posting/Models/Post.swift b/damus/Features/Posting/Models/Post.swift @@ -97,7 +97,43 @@ extension NostrPost { } } -func parse_post_blocks(content: String) -> [Block] { - return parse_note_content(content: .content(content, nil)).blocks +/// Return a list of tags +func parse_post_blocks(content: String) -> Blocks? { + let buf_size = 16000 + var buffer = Data(capacity: buf_size) + var blocks_ptr = ndb_blocks_ptr() + var ok = false + + return content.withCString { c_content -> Blocks? in + buffer.withUnsafeMutableBytes { buf in + let res = ndb_parse_content(buf, Int32(buf_size), c_content, Int32(content.utf8.count), &blocks_ptr.ptr) + ok = res != 0 + } + + guard ok else { return nil } + + let words = ndb_blocks_word_count(blocks_ptr.ptr) + let bs = collect_blocks(ptr: blocks_ptr, content: c_content) + return Blocks(words: Int(words), blocks: bs) + } } + +fileprivate func collect_blocks(ptr: ndb_blocks_ptr, content: UnsafePointer<CChar>) -> [Block] { + var i = ndb_block_iterator() + var blocks: [Block] = [] + var block_ptr = ndb_block_ptr() + + ndb_blocks_iterate_start(content, ptr.ptr, &i); + block_ptr.ptr = ndb_blocks_iterate_next(&i) + while (block_ptr.ptr != nil) { + // tags are only used for indexed mentions which aren't used in + // posts anymore, so to simplify the API let's set this to nil + if let block = Block(block: block_ptr, tags: nil) { + blocks.append(block); + } + block_ptr.ptr = ndb_blocks_iterate_next(&i) + } + + return blocks +} diff --git a/damus/Features/Profile/Views/AboutView.swift b/damus/Features/Profile/Views/AboutView.swift @@ -47,8 +47,9 @@ struct AboutView: View { } } .onAppear { - let blocks = parse_note_content(content: .content(about, nil)) - about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed + // TODO: Fix about content + //let blocks = ndb_parse_content(content: .content(about, nil)) + //about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed } } diff --git a/damus/Features/Timeline/Models/HomeModel.swift b/damus/Features/Timeline/Models/HomeModel.swift @@ -781,7 +781,7 @@ class HomeModel: ContactsDelegate { notification_status.new_events = notifs guard should_display_notification(state: damus_state, event: ev, mode: .local), - let notification_object = generate_local_notification_object(from: ev, state: damus_state) + let notification_object = generate_local_notification_object(ndb: self.damus_state.ndb, from: ev, state: damus_state) else { return } @@ -1155,7 +1155,7 @@ func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale content.title = NotificationFormatter.zap_notification_title(zap) content.body = NotificationFormatter.zap_notification_body(profiles: profiles, zap: zap, locale: locale) content.sound = UNNotificationSound.default - content.userInfo = LossyLocalNotification(type: .profile_zap, mention: .pubkey(profile_id)).to_user_info() + content.userInfo = LossyLocalNotification(type: .profile_zap, mention: .init(nip19: .npub(profile_id))).to_user_info() let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) diff --git a/damus/Features/Translations/Views/TranslateView.swift b/damus/Features/Translations/Views/TranslateView.swift @@ -159,11 +159,14 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set } // Render translated note - let translated_blocks = parse_note_content(content: .content(translated_note, event.tags)) - let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true) + // TODO: fix translated blocks + //let translated_blocks = parse_note_content(content: .content(translated_note, event.tags)) + //let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true) + return .not_needed + // and cache it - return .translated(Translated(artifacts: artifacts, language: note_lang)) + //return .translated(Translated(artifacts: artifacts, language: note_lang)) } func current_language() -> String { diff --git a/damus/Features/Zaps/Models/Zap.swift b/damus/Features/Zaps/Models/Zap.swift @@ -408,7 +408,7 @@ func invoice_to_zap_invoice(_ invoice: Invoice) -> ZapInvoice? { return nil } - return ZapInvoice(description: invoice.description, amount: amt, string: invoice.string, expiry: invoice.expiry, payment_hash: invoice.payment_hash, created_at: invoice.created_at) + return ZapInvoice(description: invoice.description, amount: amt, string: invoice.string, expiry: invoice.expiry, created_at: invoice.created_at) } func determine_zap_target(_ ev: NostrEvent) -> ZapTarget? { @@ -422,35 +422,44 @@ func determine_zap_target(_ ev: NostrEvent) -> ZapTarget? { return .profile(ptag) } - + +extension UnsafePointer<CChar> { + func as_str() -> String { + String(cString: self) + } +} + func decode_bolt11(_ s: String) -> Invoice? { - var bs = note_blocks() - bs.num_blocks = 0 - blocks_init(&bs) - let bytes = s.utf8CString + var bolt11_ptr: UnsafeMutablePointer<bolt11>? + let _ = bytes.withUnsafeBufferPointer { p in - damus_parse_content(&bs, p.baseAddress) + bolt11_ptr = bolt11_decode(nil, p.baseAddress, nil) } - - guard bs.num_blocks == 1 else { - blocks_free(&bs) + + guard let bolt11 = maybe_pointee(bolt11_ptr) else { return nil } - - let block = bs.blocks[0] - - guard let converted = Block(block) else { - blocks_free(&bs) - return nil + + var amount: Amount = .any + var desc: InvoiceDescription = .description("") + if let amt = maybe_pointee(bolt11.msat) { + amount = .specific(Int64(amt.millisatoshis)) } - - guard case .invoice(let invoice) = converted else { - blocks_free(&bs) - return nil + let expiry = bolt11.expiry + let created_at = bolt11.timestamp + + if var deschash = maybe_pointee(bolt11.description_hash) { + let data = Data(bytes: &deschash.u, count: 32) + desc = .description_hash(data) + } else { + desc = .description(bolt11.description.as_str()) } - - blocks_free(&bs) + + let invoice = Invoice(description: desc, amount: amount, string: s, expiry: expiry, created_at: created_at) + + tal_free(bolt11_ptr) + return invoice } diff --git a/damus/Models/EventRef.swift b/damus/Models/EventRef.swift @@ -0,0 +1,140 @@ +// +// EventRef.swift +// damus +// +// Created by William Casarin on 2022-05-08. +// + +import Foundation + +enum EventRef: Equatable { + case mention(Mention<NoteRef>) + case thread_id(NoteRef) + case reply(NoteRef) + case reply_to_root(NoteRef) + + var is_mention: NoteRef? { + if case .mention(let m) = self { return m.ref } + return nil + } + + var is_direct_reply: NoteRef? { + switch self { + case .mention: + return nil + case .thread_id: + return nil + case .reply(let refid): + return refid + case .reply_to_root(let refid): + return refid + } + } + + var is_thread_id: NoteRef? { + switch self { + case .mention: + return nil + case .thread_id(let referencedId): + return referencedId + case .reply: + return nil + case .reply_to_root(let referencedId): + return referencedId + } + } + + var is_reply: NoteRef? { + switch self { + case .mention: + return nil + case .thread_id: + return nil + case .reply(let refid): + return refid + case .reply_to_root(let refid): + return refid + } + } +} + +func build_mention_indices(_ blocks: BlocksSequence, type: MentionType) -> Set<Int> { + return blocks.reduce(into: []) { acc, block in + switch block { + case .mention: + return + case .mention_index(let idx): + return + case .text: + return + case .hashtag: + return + case .url: + return + case .invoice: + return + } + } +} + +func interp_event_refs_without_mentions(_ refs: [NoteRef]) -> [EventRef] { + if refs.count == 0 { + return [] + } + + if refs.count == 1 { + return [.reply_to_root(refs[0])] + } + + var evrefs: [EventRef] = [] + var first: Bool = true + for ref in refs { + if first { + evrefs.append(.thread_id(ref)) + first = false + } else { + evrefs.append(.reply(ref)) + } + } + return evrefs +} + +func interp_event_refs_with_mentions(tags: Tags) -> [EventRef] { + var mentions: [EventRef] = [] + var ev_refs: [NoteRef] = [] + var i: Int = 0 + + for tag in tags { + if let ref = NoteRef.from_tag(tag: tag) { + ev_refs.append(ref) + } + i += 1 + } + + var replies = interp_event_refs_without_mentions(ev_refs) + replies.append(contentsOf: mentions) + return replies +} + +func interpret_event_refs(blocks: BlocksSequence, tags: Tags) -> [EventRef] { + if tags.count == 0 { + return [] + } + + /// build a set of indices for each event mention + //let mention_indices = build_mention_indices(blocks, type: .e) + + /// simpler case with no mentions + //if mention_indices.count == 0 { + //return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags)) + //} + + return interp_event_refs_with_mentions(tags: tags) +} + + +func event_is_reply(_ refs: [EventRef]) -> Bool { + return refs.contains { evref in + return evref.is_reply != nil + } +} diff --git a/damus/Shared/Components/InvoiceView.swift b/damus/Shared/Components/InvoiceView.swift @@ -118,7 +118,8 @@ func getUrlToOpen(invoice: String, with wallet: Wallet.Model) throws(OpenWalletE } } -let test_invoice = Invoice(description: .description("this is a description"), amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119) + +let test_invoice = Invoice(description: .description("this is a description"), amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, created_at: 1666139119) struct InvoiceView_Previews: PreviewProvider { static var previews: some View { diff --git a/damus/Shared/Components/InvoicesView.swift b/damus/Shared/Components/InvoicesView.swift @@ -29,7 +29,7 @@ struct InvoicesView: View { struct InvoicesView_Previews: PreviewProvider { static var previews: some View { - InvoicesView(our_pubkey: test_note.pubkey, invoices: [Invoice.init(description: .description("description"), amount: .specific(10000), string: "invstr", expiry: 100000, payment_hash: Data(), created_at: 1000000)], settings: test_damus_state.settings) + InvoicesView(our_pubkey: test_note.pubkey, invoices: [Invoice.init(description: .description("description"), amount: .specific(10000), string: "invstr", expiry: 100000, created_at: 1000000)], settings: test_damus_state.settings) .frame(width: 300) } } diff --git a/damus/Shared/Utilities/Bech32Object.swift b/damus/Shared/Utilities/Bech32Object.swift @@ -10,38 +10,18 @@ import Foundation fileprivate extension String { /// Failable initializer to build a Swift.String from a C-backed `str_block_t`. init?(_ s: str_block_t) { - let len = s.end - s.start - let bytes = Data(bytes: s.start, count: len) + let bytes = Data(bytes: s.str, count: Int(s.len)) self.init(bytes: bytes, encoding: .utf8) } } struct NEvent : Equatable, Hashable { let noteid: NoteId - let relays: [String] + let relays: [RelayURL] let author: Pubkey? let kind: UInt32? - init(noteid: NoteId, relays: [String]) { - self.noteid = noteid - self.relays = relays - self.author = nil - self.kind = nil - } - - init(noteid: NoteId, relays: [String], author: Pubkey?) { - self.noteid = noteid - self.relays = relays - self.author = author - self.kind = nil - } - init(noteid: NoteId, relays: [String], kind: UInt32?) { - self.noteid = noteid - self.relays = relays - self.author = nil - self.kind = kind - } - init(noteid: NoteId, relays: [String], author: Pubkey?, kind: UInt32?) { + init(noteid: NoteId, relays: [RelayURL], author: Pubkey? = nil, kind: UInt32? = nil) { self.noteid = noteid self.relays = relays self.author = author @@ -55,17 +35,64 @@ struct NEvent : Equatable, Hashable { struct NProfile : Equatable, Hashable { let author: Pubkey - let relays: [String] + let relays: [RelayURL] } struct NAddr : Equatable, Hashable { let identifier: String let author: Pubkey - let relays: [String] + let relays: [RelayURL] let kind: UInt32 } -enum Bech32Object : Equatable { +extension ndb_relays { + func as_urls() -> [RelayURL] { + var urls = [RelayURL]() + + // + // This is so incredibly dumb but it's just what the Swift <-> C bridge + // does and I don't have a better way that doesn't involve complicated + // and slow stuff like reflection + // + let (r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20,r21,r22,r23) = self.relays + + for i in 0..<self.num_relays { + switch i { + case 0: if let relay = RelayURL(r0.as_str()) { urls.append(relay) } + case 1: if let relay = RelayURL(r1.as_str()) { urls.append(relay) } + case 2: if let relay = RelayURL(r2.as_str()) { urls.append(relay) } + case 3: if let relay = RelayURL(r3.as_str()) { urls.append(relay) } + case 4: if let relay = RelayURL(r4.as_str()) { urls.append(relay) } + case 5: if let relay = RelayURL(r5.as_str()) { urls.append(relay) } + case 6: if let relay = RelayURL(r6.as_str()) { urls.append(relay) } + case 7: if let relay = RelayURL(r7.as_str()) { urls.append(relay) } + case 8: if let relay = RelayURL(r8.as_str()) { urls.append(relay) } + case 9: if let relay = RelayURL(r9.as_str()) { urls.append(relay) } + case 10: if let relay = RelayURL(r10.as_str()) { urls.append(relay) } + case 11: if let relay = RelayURL(r11.as_str()) { urls.append(relay) } + case 12: if let relay = RelayURL(r12.as_str()) { urls.append(relay) } + case 13: if let relay = RelayURL(r13.as_str()) { urls.append(relay) } + case 14: if let relay = RelayURL(r14.as_str()) { urls.append(relay) } + case 15: if let relay = RelayURL(r15.as_str()) { urls.append(relay) } + case 16: if let relay = RelayURL(r16.as_str()) { urls.append(relay) } + case 17: if let relay = RelayURL(r17.as_str()) { urls.append(relay) } + case 18: if let relay = RelayURL(r18.as_str()) { urls.append(relay) } + case 19: if let relay = RelayURL(r19.as_str()) { urls.append(relay) } + case 20: if let relay = RelayURL(r20.as_str()) { urls.append(relay) } + case 21: if let relay = RelayURL(r21.as_str()) { urls.append(relay) } + case 22: if let relay = RelayURL(r22.as_str()) { urls.append(relay) } + case 23: if let relay = RelayURL(r23.as_str()) { urls.append(relay) } + default: + break + } + } + + return urls + } + +} + +enum Bech32Object : Equatable, Hashable { case nsec(Privkey) case npub(Pubkey) case note(NoteId) @@ -75,26 +102,68 @@ enum Bech32Object : Equatable { case nrelay(String) case naddr(NAddr) + init?(block: ndb_mention_bech32_block) { + let b32 = block.bech32 + switch block.bech32_type { + case .note: + let data = b32.note.event_id.as_data(size: 32) + self = .note(NoteId(data)) + case .npub: + let data = b32.npub.pubkey.as_data(size: 32) + self = .npub(Pubkey(data)) + case .nprofile: + let pk = b32.nprofile.pubkey.as_data(size: 32) + let relays = b32.nprofile.relays.as_urls() + self = .nprofile(NProfile(author: Pubkey(pk), relays: relays)) + case .nevent: + let nevent = b32.nevent + let note_id = NoteId(nevent.event_id.as_data(size: 32)) + let relays = nevent.relays.as_urls() + var author: Pubkey? = nil + if nevent.pubkey != nil { + author = Pubkey(nevent.pubkey.as_data(size: 32)) + } + var kind: UInt32? = nil + if nevent.has_kind != 0 { + kind = nevent.kind + } + + self = .nevent(NEvent(noteid: note_id, relays: relays, author: author, kind: kind)) + case .nrelay: + self = .nrelay(b32.nrelay.relay.as_str()) + case .naddr: + let identifier = b32.naddr.identifier.as_str() + let author = Pubkey(b32.naddr.pubkey.as_data(size: 32)) + let relays = b32.naddr.relays.as_urls() + self = .naddr(NAddr(identifier: identifier, author: author, relays: relays, kind: b32.naddr.kind)) + case .nsec: + return nil + case .none: + return nil + } + } + static func parse(_ str: String) -> Bech32Object? { if str.starts(with: "nscript"), let decoded = try? bech32_decode(str) { return .nscript(decoded.data.bytes) } var b: nostr_bech32_t = nostr_bech32() + var bytes = Data(capacity: str.utf8.count) - let bytes = Array(str.utf8) - - bytes.withUnsafeBufferPointer { buffer in - guard let baseAddress = buffer.baseAddress else { return } - - var cursorInstance = cursor() - cursorInstance.start = UnsafeMutablePointer(mutating: baseAddress) - cursorInstance.p = UnsafeMutablePointer(mutating: baseAddress) - cursorInstance.end = cursorInstance.start.advanced(by: buffer.count) - - parse_nostr_bech32(&cursorInstance, &b) + let ok = str.withCString { cstr in + let ok = bytes.withUnsafeMutableBytes { buffer -> Int32 in + guard let addr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { + return 0 + } + return parse_nostr_bech32(addr, Int32(buffer.count), cstr, str.utf8.count, &b) + } + + return ok != 0 } - + + guard ok else { return nil } + return decodeCBech32(b) } @@ -117,25 +186,7 @@ enum Bech32Object : Equatable { } func toMentionRef() -> MentionRef? { - switch self { - case .nsec(let privkey): - guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } - return .pubkey(pubkey) - case .npub(let pubkey): - return .pubkey(pubkey) - case .note(let noteid): - return .note(noteid) - case .nscript(_): - return nil - case .nevent(let nevent): - return .nevent(nevent) - case .nprofile(let nprofile): - return .nprofile(nprofile) - case .nrelay(let relayURL): - return .nrelay(relayURL) - case .naddr(let naddr): - return .naddr(naddr) - } + MentionRef(nip19: self) } } @@ -143,75 +194,37 @@ enum Bech32Object : Equatable { func decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? { switch b.type { case NOSTR_BECH32_NOTE: - let note = b.data.note; - let note_id = NoteId(Data(bytes: note.event_id, count: 32)) + let note_id = NoteId(Data(bytes: b.note.event_id, count: 32)) return .note(note_id) case NOSTR_BECH32_NEVENT: - let nevent = b.data.nevent; - let note_id = NoteId(Data(bytes: nevent.event_id, count: 32)) - let pubkey = nevent.pubkey != nil ? Pubkey(Data(bytes: nevent.pubkey, count: 32)) : nil - let kind: UInt32? = nevent.has_kind ? nevent.kind : nil - let relays = getRelayStrings(from: nevent.relays) + let note_id = NoteId(Data(bytes: b.nevent.event_id, count: 32)) + let pubkey = b.nevent.pubkey != nil ? Pubkey(Data(bytes: b.nevent.pubkey, count: 32)) : nil + let kind: UInt32? = b.nevent.has_kind == 0 ? nil : b.nevent.kind + let relays = b.nevent.relays.as_urls() return .nevent(NEvent(noteid: note_id, relays: relays, author: pubkey, kind: kind)) case NOSTR_BECH32_NPUB: - let npub = b.data.npub - let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32)) + let pubkey = Pubkey(Data(bytes: b.npub.pubkey, count: 32)) return .npub(pubkey) case NOSTR_BECH32_NSEC: - let nsec = b.data.nsec - let privkey = Privkey(Data(bytes: nsec.nsec, count: 32)) + let privkey = Privkey(Data(bytes: b.nsec.nsec, count: 32)) guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil } return .npub(pubkey) case NOSTR_BECH32_NPROFILE: - let nprofile = b.data.nprofile - let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32)) - return .nprofile(NProfile(author: pubkey, relays: getRelayStrings(from: nprofile.relays))) + let pubkey = Pubkey(Data(bytes: b.nprofile.pubkey, count: 32)) + return .nprofile(NProfile(author: pubkey, relays: b.nprofile.relays.as_urls())) case NOSTR_BECH32_NRELAY: - let nrelay = b.data.nrelay - let str_relay: str_block = nrelay.relay - guard let relay_str = String(str_relay) else { - return nil - } - return .nrelay(relay_str) + return .nrelay(b.nrelay.relay.as_str()) case NOSTR_BECH32_NADDR: - let naddr = b.data.naddr - guard let identifier = String(naddr.identifier) else { - return nil - } - let pubkey = Pubkey(Data(bytes: naddr.pubkey, count: 32)) - let kind = naddr.kind - - return .naddr(NAddr(identifier: identifier, author: pubkey, relays: getRelayStrings(from: naddr.relays), kind: kind)) + let pubkey = Pubkey(Data(bytes: b.naddr.pubkey, count: 32)) + let kind = b.naddr.kind + let identifier = b.naddr.identifier.as_str() + + return .naddr(NAddr(identifier: identifier, author: pubkey, relays: b.naddr.relays.as_urls(), kind: kind)) default: return nil } } -private func getRelayStrings(from relays: relays) -> [String] { - var result = [String]() - let numRelays = Int(relays.num_relays) - - func processRelay(_ relay: str_block) { - if let string = String(relay) { - result.append(string) - } - } - - // Since relays is a C tuple, the indexes can't be iterated through so they need to be manually processed - if numRelays > 0 { processRelay(relays.relays.0) } - if numRelays > 1 { processRelay(relays.relays.1) } - if numRelays > 2 { processRelay(relays.relays.2) } - if numRelays > 3 { processRelay(relays.relays.3) } - if numRelays > 4 { processRelay(relays.relays.4) } - if numRelays > 5 { processRelay(relays.relays.5) } - if numRelays > 6 { processRelay(relays.relays.6) } - if numRelays > 7 { processRelay(relays.relays.7) } - if numRelays > 8 { processRelay(relays.relays.8) } - if numRelays > 9 { processRelay(relays.relays.9) } - - return result -} - private enum TLVType: UInt8 { case SPECIAL case RELAY @@ -225,9 +238,9 @@ private func writeBytesList(bytesList: inout [UInt8], tlvType: TLVType, data: [U bytesList.append(contentsOf: data.bytes) } -private func writeBytesRelays(bytesList: inout [UInt8], relays: [String]) { - for relay in relays where !relay.isEmpty { - guard let relayData = relay.data(using: .utf8) else { +private func writeBytesRelays(bytesList: inout [UInt8], relays: [RelayURL]) { + for relay in relays { + guard let relayData = relay.url.absoluteString.data(using: .utf8) else { continue // skip relay if can't read data } writeBytesList(bytesList: &bytesList, tlvType: .RELAY, data: relayData.bytes) diff --git a/damus/Shared/Utilities/EventCache.swift b/damus/Shared/Utilities/EventCache.swift @@ -376,7 +376,7 @@ func preload_event(plan: PreloadPlan, state: DamusState) async { //print("Preloading event \(plan.event.content)") if artifacts == nil && plan.load_artifacts { - let arts = render_note_content(ev: plan.event, profiles: profiles, keypair: our_keypair) + let arts = render_note_content(ndb: state.ndb, ev: plan.event, profiles: profiles, keypair: our_keypair) artifacts = arts // we need these asap @@ -397,7 +397,7 @@ func preload_event(plan: PreloadPlan, state: DamusState) async { } if plan.load_preview, note_artifact_is_separated(kind: plan.event.known_kind) { - let arts = artifacts ?? render_note_content(ev: plan.event, profiles: profiles, keypair: our_keypair) + let arts = artifacts ?? render_note_content(ndb: state.ndb, ev: plan.event, profiles: profiles, keypair: our_keypair) // only separated artifacts have previews if case .separated(let sep) = arts { @@ -412,7 +412,7 @@ func preload_event(plan: PreloadPlan, state: DamusState) async { } } - let note_language = plan.data.translations_model.note_language ?? plan.event.note_language(our_keypair) ?? current_language() + let note_language = plan.data.translations_model.note_language ?? plan.event.note_language(ndb: state.ndb, our_keypair) ?? current_language() var translations: TranslateStatus? = nil // We have to recheck should_translate here now that we have note_language diff --git a/damus/Shared/Utilities/LocalNotification.swift b/damus/Shared/Utilities/LocalNotification.swift @@ -25,7 +25,7 @@ struct LossyLocalNotification { return self.from(json_encoded_ndb_note: encoded_ndb_note) } guard let id = user_info["id"] as? String, - let target_id = MentionRef.from_bech32(str: id) else { + let target_id = MentionRef(bech32_str: id) else { return nil } let typestr = user_info["type"] as! String @@ -42,8 +42,11 @@ struct LossyLocalNotification { } static func from(ndb_note: NdbNote) -> LossyLocalNotification? { - guard let known_kind = ndb_note.known_kind, let type = LocalNotificationType.from(nostr_kind: known_kind) else { return nil } - let target: MentionRef = .note(ndb_note.id) + guard let known_kind = ndb_note.known_kind, + let type = LocalNotificationType.from(nostr_kind: known_kind) else { + return nil + } + let target: MentionRef = .init(nip19: .note(ndb_note.id)) return LossyLocalNotification(type: type, mention: target) } } @@ -69,7 +72,7 @@ struct LocalNotification { let content: String func to_lossy() -> LossyLocalNotification { - return LossyLocalNotification(type: self.type, mention: .note(self.target.id)) + return LossyLocalNotification(type: self.type, mention: .init(nip19: .note(self.target.id))) } } diff --git a/damus/TestData.swift b/damus/TestData.swift @@ -54,7 +54,7 @@ let test_repost_2 = NostrEvent(content: test_encoded_post, keypair: test_keypair let test_reposts = [test_repost_1, test_repost_2] let test_event_group = EventGroup(events: test_reposts) -let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000) +let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, created_at: 1000000) let test_zap_request_ev = NostrEvent(content: "hi", keypair: test_keypair, kind: 9734)! let test_zap_request = ZapRequest(ev: test_zap_request_ev) let test_zap = Zap(event: test_note, invoice: test_zap_invoice, zapper: test_note.pubkey, target: .profile(test_pubkey), raw_request: test_zap_request, is_anon: false, private_request: nil) diff --git a/nostrdb/Ndb.swift b/nostrdb/Ndb.swift @@ -206,13 +206,28 @@ class Ndb { self.ndb = db return true } + + func lookup_blocks_by_key_with_txn<Y>(_ key: NoteKey, txn: NdbTxn<Y>) -> NdbBlocks? { + guard let blocks = ndb_get_blocks_by_key(self.ndb.ndb, &txn.txn, key) else { + return nil + } + + return NdbBlocks(ptr: blocks) + } + + func lookup_blocks_by_key(_ key: NoteKey) -> NdbTxn<NdbBlocks?>? { + NdbTxn(ndb: self) { txn in + lookup_blocks_by_key_with_txn(key, txn: txn) + } + } func lookup_note_by_key_with_txn<Y>(_ key: NoteKey, txn: NdbTxn<Y>) -> NdbNote? { var size: Int = 0 guard let note_p = ndb_get_note_by_key(&txn.txn, key, &size) else { return nil } - return NdbNote(note: note_p, size: size, owned: false, key: key) + let ptr = ndb_note_ptr(ptr: note_p) + return NdbNote(note: ptr, size: size, owned: false, key: key) } func text_search(query: String, limit: Int = 128, order: NdbSearchOrder = .newest_first) -> [NoteKey] { @@ -403,7 +418,8 @@ class Ndb { let note_p = ndb_get_note_by_id(&txn.txn, baseAddress, &size, &key) else { return nil } - return NdbNote(note: note_p, size: size, owned: false, key: key) + let ptr = ndb_note_ptr(ptr: note_p) + return NdbNote(note: ptr, size: size, owned: false, key: key) } } diff --git a/nostrdb/NdbBlock.swift b/nostrdb/NdbBlock.swift @@ -0,0 +1,124 @@ +// +// NdbBlock.swift +// damus +// +// Created by William Casarin on 2024-01-25. +// + +import Foundation + +enum NdbBlockType: UInt32 { + case hashtag = 1 + case text = 2 + case mention_index = 3 + case mention_bech32 = 4 + case url = 5 + case invoice = 6 +} + +extension ndb_mention_bech32_block { + var bech32_type: NdbBech32Type? { + NdbBech32Type(rawValue: self.bech32.type.rawValue) + } +} + +enum NdbBech32Type: UInt32 { + case note = 1 + case npub = 2 + case nprofile = 3 + case nevent = 4 + case nrelay = 5 + case naddr = 6 + case nsec = 7 + + var is_notelike: Bool { + return self == .note || self == .nevent + } +} + +extension ndb_invoice_block { + func as_invoice() -> Invoice? { + let b11 = self.invoice + let invstr = self.invstr.as_str() + + guard let description = convert_invoice_description(b11: b11) else { + return nil + } + + let amount: Amount = b11.amount == 0 ? .any : .specific(Int64(b11.amount)) + + return Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, created_at: b11.timestamp) + } +} + +enum NdbBlock { + case text(ndb_str_block) + case mention(ndb_mention_bech32_block) + case hashtag(ndb_str_block) + case url(ndb_str_block) + case invoice(ndb_invoice_block) + case mention_index(UInt32) + + init?(_ ptr: ndb_block_ptr) { + guard let type = NdbBlockType(rawValue: ndb_get_block_type(ptr.ptr).rawValue) else { + return nil + } + switch type { + case .hashtag: self = .hashtag(ptr.block.str) + case .text: self = .text(ptr.block.str) + case .invoice: self = .invoice(ptr.block.invoice) + case .url: self = .url(ptr.block.str) + case .mention_bech32: self = .mention(ptr.block.mention_bech32) + case .mention_index: self = .mention_index(ptr.block.mention_index) + } + } + + var is_previewable: Bool { + switch self { + case .mention(let m): + switch m.bech32_type { + case .note, .nevent: return true + default: return false + } + case .invoice: + return true + case .url: + return true + default: + return false + } + } + + static func convertToStringCopy(from block: str_block_t) -> String? { + guard let cString = block.str else { + return nil + } + let byteBuffer = UnsafeBufferPointer(start: cString, count: Int(block.len)).map { UInt8(bitPattern: $0) } + + // Create a Swift String from the byte array + return String(bytes: byteBuffer, encoding: .utf8) + } +} + + +struct NdbBlocks { + private let blocks_ptr: ndb_blocks_ptr + + init(ptr: OpaquePointer?) { + self.blocks_ptr = ndb_blocks_ptr(ptr: ptr) + } + + var words: Int { + Int(ndb_blocks_word_count(blocks_ptr.ptr)) + } + + func iter(note: NdbNote) -> BlocksSequence { + BlocksSequence(note: note, blocks: self) + } + + func as_ptr() -> OpaquePointer? { + return self.blocks_ptr.ptr + } +} + + diff --git a/nostrdb/NdbBlocksIterator.swift b/nostrdb/NdbBlocksIterator.swift @@ -0,0 +1,59 @@ +// +// NdbBlockIterator.swift +// damus +// +// Created by William Casarin on 2024-01-25. +// + +import Foundation + + +struct BlocksIterator: IteratorProtocol { + typealias Element = NdbBlock + + var done: Bool + var iter: ndb_block_iterator + var note: NdbNote + + mutating func next() -> NdbBlock? { + guard iter.blocks != nil, + let ptr = ndb_blocks_iterate_next(&iter) else { + done = true + return nil + } + + let block_ptr = ndb_block_ptr(ptr: ptr) + return NdbBlock(block_ptr) + } + + init(note: NdbNote, blocks: NdbBlocks) { + let content = ndb_note_content(note.note.ptr) + self.iter = ndb_block_iterator(content: content, blocks: nil, block: ndb_block(), p: nil) + ndb_blocks_iterate_start(content, blocks.as_ptr(), &self.iter) + self.done = false + self.note = note + } +} + +struct BlocksSequence: Sequence { + let blocks: NdbBlocks + let note: NdbNote + + init(note: NdbNote, blocks: NdbBlocks) { + self.blocks = blocks + self.note = note + } + + func makeIterator() -> BlocksIterator { + return .init(note: note, blocks: blocks) + } + + func collect() -> [NdbBlock] { + var xs = [NdbBlock]() + for x in self { + xs.append(x) + } + return xs + } +} + diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift @@ -47,7 +47,7 @@ class NdbNote: Codable, Equatable, Hashable { private(set) var owned: Bool let count: Int let key: NoteKey? - let note: UnsafeMutablePointer<ndb_note> + let note: ndb_note_ptr // cached stuff (TODO: remove these) var decrypted_content: String? = nil @@ -58,7 +58,7 @@ class NdbNote: Codable, Equatable, Hashable { } } - init(note: UnsafeMutablePointer<ndb_note>, size: Int, owned: Bool, key: NoteKey?) { + init(note: ndb_note_ptr, size: Int, owned: Bool, key: NoteKey?) { self.note = note self.owned = owned self.count = size @@ -80,9 +80,9 @@ class NdbNote: Codable, Equatable, Hashable { } let buf = malloc(self.count)! - memcpy(buf, &self.note.pointee, self.count) - let new_note = buf.assumingMemoryBound(to: ndb_note.self) + memcpy(buf, UnsafeRawPointer(self.note.ptr), self.count) + let new_note = ndb_note_ptr(ptr: OpaquePointer(buf)) return NdbNote(note: new_note, size: self.count, owned: true, key: self.key) } @@ -95,16 +95,16 @@ class NdbNote: Codable, Equatable, Hashable { } var content_raw: UnsafePointer<CChar> { - ndb_note_content(note) + ndb_note_content(note.ptr) } var content_len: UInt32 { - ndb_note_content_length(note) + ndb_note_content_length(note.ptr) } /// NDBTODO: make this into data var id: NoteId { - .init(Data(bytes: ndb_note_id(note), count: 32)) + .init(Data(bytes: ndb_note_id(note.ptr), count: 32)) } var raw_note_id: UnsafeMutablePointer<UInt8> { @@ -116,20 +116,20 @@ class NdbNote: Codable, Equatable, Hashable { } var sig: Signature { - .init(Data(bytes: ndb_note_sig(note), count: 64)) + .init(Data(bytes: ndb_note_sig(note.ptr), count: 64)) } /// NDBTODO: make this into data var pubkey: Pubkey { - .init(Data(bytes: ndb_note_pubkey(note), count: 32)) + .init(Data(bytes: ndb_note_pubkey(note.ptr), count: 32)) } var created_at: UInt32 { - ndb_note_created_at(note) + ndb_note_created_at(note.ptr) } var kind: UInt32 { - ndb_note_kind(note) + ndb_note_kind(note.ptr) } var tags: TagsSequence { @@ -144,7 +144,7 @@ class NdbNote: Codable, Equatable, Hashable { print("\(NdbNote.notes_created) ndb_notes, \(NdbNote.total_ndb_size) bytes") #endif - free(note) + free(UnsafeMutableRawPointer(note.ptr)) } } @@ -234,7 +234,7 @@ class NdbNote: Codable, Equatable, Hashable { let buflen = MAX_NOTE_SIZE let buf = malloc(buflen) - ndb_builder_init(&builder, buf, Int32(buflen)) + ndb_builder_init(&builder, buf, buflen) var pk_raw = noteConstructionMaterial.pubkey.bytes @@ -262,9 +262,27 @@ class NdbNote: Codable, Equatable, Hashable { return nil } - var n = UnsafeMutablePointer<ndb_note>?(nil) + var n = ndb_note_ptr() + + var the_kp: ndb_keypair? = nil + + if let sec = keypair.privkey { + var kp = ndb_keypair() + memcpy(&kp.secret.0, sec.id.bytes, 32); + + if ndb_create_keypair(&kp) <= 0 { + print("bad keypair") + } else { + the_kp = kp + } + } var len: Int32 = 0 + if var the_kp { + len = ndb_builder_finalize(&builder, &n.ptr, &the_kp) + } else { + len = ndb_builder_finalize(&builder, &n.ptr, nil) + } switch noteConstructionMaterial { case .keypair(let keypair): @@ -328,7 +346,7 @@ class NdbNote: Codable, Equatable, Hashable { return nil } - self.note = r.assumingMemoryBound(to: ndb_note.self) + self.note = ndb_note_ptr(ptr: OpaquePointer(r)) self.key = nil } @@ -352,9 +370,9 @@ class NdbNote: Codable, Equatable, Hashable { //guard var json_cstr = json.cString(using: .utf8) else { return nil } //json_cs - var note: UnsafeMutablePointer<ndb_note>? - - let len = ndb_note_from_json(json, Int32(json_len), ¬e, data, Int32(bufsize)) + var note = ndb_note_ptr() + + let len = ndb_note_from_json(json, Int32(json_len), ¬e.ptr, data, Int32(bufsize)) if len == 0 { free(data) @@ -362,10 +380,9 @@ class NdbNote: Codable, Equatable, Hashable { } // Create new Data with just the valid bytes - guard let note_data = realloc(data, Int(len)) else { return nil } - let new_note = note_data.assumingMemoryBound(to: ndb_note.self) - - return NdbNote(note: new_note, size: Int(len), owned: true, key: nil) + guard let new_note = realloc(data, Int(len)) else { return nil } + let new_note_ptr = ndb_note_ptr(ptr: OpaquePointer(new_note)) + return NdbNote(note: new_note_ptr, size: Int(len), owned: true, key: nil) } func get_inner_event() -> NdbNote? { @@ -397,7 +414,7 @@ extension NdbNote { var should_show_event: Bool { return !too_big } - + func is_hellthread(max_pubkeys: Int) -> Bool { switch known_kind { case .text, .boost, .like, .zap: @@ -407,10 +424,6 @@ extension NdbNote { } } - func get_blocks(keypair: Keypair) -> Blocks { - return parse_note_content(content: .init(note: self, keypair: keypair)) - } - // TODO: References iterator public var referenced_ids: References<NoteId> { References<NoteId>(tags: self.tags) @@ -451,7 +464,7 @@ extension NdbNote { public var references: References<RefId> { References<RefId>(tags: self.tags) } - + func thread_reply() -> ThreadReply? { if self.known_kind != .highlight { return ThreadReply(tags: self.tags) @@ -463,6 +476,21 @@ extension NdbNote { return ThreadReply(tags: self.tags)?.reply.note_id } + func blocks(ndb: Ndb) -> NdbTxn<NdbBlocks>? { + let blocks_txn = NdbTxn<NdbBlocks?>(ndb: ndb) { txn in + guard let key = ndb.lookup_note_key_with_txn(self.id, txn: txn) else { + return nil + } + return ndb.lookup_blocks_by_key_with_txn(key, txn: txn) + } + + guard let blocks_txn else { + return nil + } + + return blocks_txn.collect() + } + func get_content(_ keypair: Keypair) -> String { if known_kind == .dm { return decrypted(keypair: keypair) ?? "*failed to decrypt content*" @@ -482,10 +510,6 @@ extension NdbNote { return content } - func blocks(_ keypair: Keypair) -> Blocks { - return get_blocks(keypair: keypair) - } - // NDBTODO: switch this to operating on bytes not strings func decrypted(keypair: Keypair) -> String? { if let decrypted_content { @@ -526,34 +550,22 @@ extension NdbNote { return self.referenced_ids.last } - // NDBTODO: id -> data - /* - public func references(id: String, key: AsciiCharacter) -> Bool { - var matcher: (Reference) -> Bool = { ref in ref.ref_id.matches_str(id) } - if id.count == 64, let decoded = hex_decode(id) { - matcher = { ref in ref.ref_id.matches_id(decoded) } - } - for ref in References(tags: self.tags) { - if ref.key == key && matcher(ref) { - return true - } - } - - return false - } - */ - func is_reply() -> Bool { return thread_reply() != nil } - func note_language(_ keypair: Keypair) -> String? { + func note_language(ndb: Ndb, _ keypair: Keypair) -> String? { assert(!Thread.isMainThread, "This function must not be run on the main thread.") // Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in // and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer. - let originalBlocks = self.blocks(keypair).blocks - let originalOnlyText = originalBlocks.compactMap { + /* + guard let blocks_txn = self.blocks(ndb: ndb) else { + return nil + } + let blocks = blocks_txn.unsafeUnownedValue + + let originalOnlyText = blocks.blocks(note: self).compactMap { if case .text(let txt) = $0 { // Replacing right single quotation marks (’) with "typewriter or ASCII apostrophes" (') // as a workaround to get Apple's language recognizer to predict language the correctly. @@ -580,6 +592,9 @@ extension NdbNote { } } .joined(separator: " ") + */ + + let originalOnlyText = self.get_content(keypair) // If there is no text, there's nothing to use to detect language. guard !originalOnlyText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { diff --git a/nostrdb/NdbTagElem.swift b/nostrdb/NdbTagElem.swift @@ -25,7 +25,7 @@ struct NdbStrIter: IteratorProtocol { } init(tag: NdbTagElem) { - self.str = ndb_tag_str(tag.note.note, tag.tag, tag.index) + self.str = ndb_tag_str(tag.note.note.ptr, tag.tag.ptr, tag.index) self.ind = 0 self.tag = tag } @@ -33,7 +33,7 @@ struct NdbStrIter: IteratorProtocol { struct NdbTagElem: Sequence, Hashable, Equatable { let note: NdbNote - let tag: UnsafeMutablePointer<ndb_tag> + let tag: ndb_tag_ptr let index: Int32 let str: ndb_str @@ -59,11 +59,11 @@ struct NdbTagElem: Sequence, Hashable, Equatable { return memcmp(lhs.str.str, rhs.str.str, r) == 0 } - init(note: NdbNote, tag: UnsafeMutablePointer<ndb_tag>, index: Int32) { + init(note: NdbNote, tag: ndb_tag_ptr, index: Int32) { self.note = note self.tag = tag self.index = index - self.str = ndb_tag_str(note.note, tag, index) + self.str = ndb_tag_str(note.note.ptr, tag.ptr, index) } var is_id: Bool { diff --git a/nostrdb/NdbTagIterator.swift b/nostrdb/NdbTagIterator.swift @@ -21,10 +21,10 @@ import Foundation /// ``` struct TagSequence: Sequence { let note: NdbNote - let tag: UnsafeMutablePointer<ndb_tag> + let tag: ndb_tag_ptr var count: UInt16 { - tag.pointee.count + ndb_tag_count(tag.ptr) } func strings() -> [String] { @@ -46,7 +46,7 @@ struct TagIterator: IteratorProtocol { typealias Element = NdbTagElem mutating func next() -> NdbTagElem? { - guard index < tag.pointee.count else { return nil } + guard index < ndb_tag_count(tag.ptr) else { return nil } let el = NdbTagElem(note: note, tag: tag, index: index) index += 1 @@ -56,13 +56,13 @@ struct TagIterator: IteratorProtocol { var index: Int32 let note: NdbNote - var tag: UnsafeMutablePointer<ndb_tag> + var tag: ndb_tag_ptr var count: UInt16 { - tag.pointee.count + ndb_tag_count(tag.ptr) } - init(note: NdbNote, tag: UnsafeMutablePointer<ndb_tag>) { + init(note: NdbNote, tag: ndb_tag_ptr) { self.note = note self.tag = tag self.index = 0 diff --git a/nostrdb/NdbTagsIterator.swift b/nostrdb/NdbTagsIterator.swift @@ -20,16 +20,13 @@ struct TagsIterator: IteratorProtocol { return nil } - return TagSequence(note: note, tag: self.iter.tag) - } - - var count: UInt16 { - return note.note.pointee.tags.count + let tag_ptr = ndb_tag_ptr(ptr: self.iter.tag) + return TagSequence(note: note, tag: tag_ptr) } init(note: NdbNote) { self.iter = ndb_iterator() - ndb_tags_iterate_start(note.note, &self.iter) + ndb_tags_iterate_start(note.note.ptr, &self.iter) self.done = false self.note = note } @@ -39,7 +36,8 @@ struct TagsSequence: Encodable, Sequence { let note: NdbNote var count: UInt16 { - note.note.pointee.tags.count + let tags_ptr = ndb_note_tags(note.note.ptr) + return ndb_tags_count(tags_ptr) } func strings() -> [[String]] { @@ -70,7 +68,8 @@ struct TagsSequence: Encodable, Sequence { } precondition(false, "sequence subscript oob") // it seems like the compiler needs this or it gets bitchy - return .init(note: .init(note: .allocate(capacity: 1), size: 0, owned: true, key: nil), tag: .allocate(capacity: 1)) + let nil_ptr = OpaquePointer(bitPattern: 0) + return .init(note: .init(note: .init(ptr: nil_ptr), size: 0, owned: true, key: nil), tag: .init(ptr: nil_ptr)) } func makeIterator() -> TagsIterator { diff --git a/nostrdb/NdbTxn.swift b/nostrdb/NdbTxn.swift @@ -21,6 +21,10 @@ class NdbTxn<T> { var generation: Int var name: String + static func pure(ndb: Ndb, val: T) -> NdbTxn<T> { + .init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn") + } + init?(ndb: Ndb, with: (NdbTxn<T>) -> T = { _ in () }, name: String? = nil) { guard !ndb.is_closed else { return nil } self.name = name ?? "txn" diff --git a/nostrdb/src/bindings/c/flatbuffers_common_builder.h b/nostrdb/src/bindings/c/flatbuffers_common_builder.h @@ -5,9 +5,9 @@ /* Common FlatBuffers build functionality for C. */ -#include "flatcc/flatcc_prologue.h" +#include "flatcc_prologue.h" #ifndef FLATBUILDER_H -#include "flatcc/flatcc_builder.h" +#include "flatcc_builder.h" #endif typedef flatcc_builder_t flatbuffers_builder_t; typedef flatcc_builder_ref_t flatbuffers_ref_t; @@ -681,5 +681,5 @@ __flatbuffers_build_scalar(flatbuffers_, flatbuffers_double, double) __flatbuffers_build_string(flatbuffers_) __flatbuffers_build_buffer(flatbuffers_) -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* FLATBUFFERS_COMMON_BUILDER_H */ diff --git a/nostrdb/src/bindings/c/flatbuffers_common_reader.h b/nostrdb/src/bindings/c/flatbuffers_common_reader.h @@ -5,8 +5,8 @@ /* Common FlatBuffers read functionality for C. */ -#include "flatcc/flatcc_prologue.h" -#include "flatcc/flatcc_flatbuffers.h" +#include "flatcc_prologue.h" +#include "flatcc_flatbuffers.h" #define __flatbuffers_read_scalar_at_byteoffset(N, p, o) N ## _read_from_pe((uint8_t *)(p) + (o)) @@ -574,5 +574,5 @@ static inline N ## _ ## K ## t N ## _as_typed_root(const void *buffer__tmp)\ #define __flatbuffers_struct_as_root(N) __flatbuffers_buffer_as_root(N, struct_) #define __flatbuffers_table_as_root(N) __flatbuffers_buffer_as_root(N, table_) -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* FLATBUFFERS_COMMON_H */ diff --git a/nostrdb/src/bindings/c/meta_builder.h b/nostrdb/src/bindings/c/meta_builder.h @@ -9,7 +9,7 @@ #ifndef FLATBUFFERS_COMMON_BUILDER_H #include "flatbuffers_common_builder.h" #endif -#include "flatcc/flatcc_prologue.h" +#include "flatcc_prologue.h" #ifndef flatbuffers_identifier #define flatbuffers_identifier 0 #endif @@ -65,5 +65,5 @@ static NdbEventMeta_ref_t NdbEventMeta_clone(flatbuffers_builder_t *B, NdbEventM __flatbuffers_memoize_end(B, t, NdbEventMeta_end(B)); } -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* META_BUILDER_H */ diff --git a/nostrdb/src/bindings/c/meta_reader.h b/nostrdb/src/bindings/c/meta_reader.h @@ -6,11 +6,11 @@ #ifndef FLATBUFFERS_COMMON_READER_H #include "flatbuffers_common_reader.h" #endif -#include "flatcc/flatcc_flatbuffers.h" +#include "flatcc_flatbuffers.h" #ifndef __alignas_is_defined #include <stdalign.h> #endif -#include "flatcc/flatcc_prologue.h" +#include "flatcc_prologue.h" #ifndef flatbuffers_identifier #define flatbuffers_identifier 0 #endif @@ -54,5 +54,5 @@ __flatbuffers_define_scalar_field(4, NdbEventMeta, zaps, flatbuffers_int32, int3 __flatbuffers_define_scalar_field(5, NdbEventMeta, zap_total, flatbuffers_int64, int64_t, INT64_C(0)) -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* META_READER_H */ diff --git a/nostrdb/src/bindings/c/profile_builder.h b/nostrdb/src/bindings/c/profile_builder.h @@ -9,7 +9,7 @@ #ifndef FLATBUFFERS_COMMON_BUILDER_H #include "flatbuffers_common_builder.h" #endif -#include "flatcc/flatcc_prologue.h" +#include "flatcc_prologue.h" #ifndef flatbuffers_identifier #define flatbuffers_identifier 0 #endif @@ -127,5 +127,5 @@ static NdbProfileRecord_ref_t NdbProfileRecord_clone(flatbuffers_builder_t *B, N __flatbuffers_memoize_end(B, t, NdbProfileRecord_end(B)); } -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* PROFILE_BUILDER_H */ diff --git a/nostrdb/src/bindings/c/profile_json_parser.h b/nostrdb/src/bindings/c/profile_json_parser.h @@ -3,8 +3,8 @@ /* Generated by flatcc 0.6.1 FlatBuffers schema compiler for C by dvide.com */ -#include "flatcc/flatcc_json_parser.h" -#include "flatcc/flatcc_prologue.h" +#include "flatcc_json_parser.h" +#include "flatcc_prologue.h" /* * Parses the default root table or struct of the schema and constructs a FlatBuffer. @@ -408,5 +408,5 @@ static int profile_parse_json(flatcc_builder_t *B, flatcc_json_parser_t *ctx, return 0; } -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* PROFILE_JSON_PARSER_H */ diff --git a/nostrdb/src/bindings/c/profile_reader.h b/nostrdb/src/bindings/c/profile_reader.h @@ -6,11 +6,11 @@ #ifndef FLATBUFFERS_COMMON_READER_H #include "flatbuffers_common_reader.h" #endif -#include "flatcc/flatcc_flatbuffers.h" +#include "flatcc_flatbuffers.h" #ifndef __alignas_is_defined #include <stdalign.h> #endif -#include "flatcc/flatcc_prologue.h" +#include "flatcc_prologue.h" #ifndef flatbuffers_identifier #define flatbuffers_identifier 0 #endif @@ -89,5 +89,5 @@ __flatbuffers_define_scalar_field(2, NdbProfileRecord, note_key, flatbuffers_uin __flatbuffers_define_string_field(3, NdbProfileRecord, lnurl, 0) -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* PROFILE_READER_H */ diff --git a/nostrdb/src/bindings/c/profile_verifier.h b/nostrdb/src/bindings/c/profile_verifier.h @@ -6,8 +6,8 @@ #ifndef PROFILE_READER_H #include "profile_reader.h" #endif -#include "flatcc/flatcc_verifier.h" -#include "flatcc/flatcc_prologue.h" +#include "flatcc_verifier.h" +#include "flatcc_prologue.h" static int NdbProfile_verify_table(flatcc_table_verifier_descriptor_t *td); static int NdbProfileRecord_verify_table(flatcc_table_verifier_descriptor_t *td); @@ -80,5 +80,5 @@ static inline int NdbProfileRecord_verify_as_root_with_type_hash(const void *buf return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &NdbProfileRecord_verify_table); } -#include "flatcc/flatcc_epilogue.h" +#include "flatcc_epilogue.h" #endif /* PROFILE_VERIFIER_H */ diff --git a/nostrdb/src/bindings/swift/NdbMeta.swift b/nostrdb/src/bindings/swift/NdbMeta.swift @@ -2,8 +2,6 @@ // swiftlint:disable all // swiftformat:disable all -import FlatBuffers - public struct NdbEventMeta: FlatBufferObject, Verifiable { static func validateVersion() { FlatBuffersVersion_23_5_26() } diff --git a/nostrdb/src/bindings/swift/NdbProfile.swift b/nostrdb/src/bindings/swift/NdbProfile.swift @@ -2,8 +2,6 @@ // swiftlint:disable all // swiftformat:disable all -import FlatBuffers - public struct NdbProfile: FlatBufferObject, Verifiable { static func validateVersion() { FlatBuffersVersion_23_5_26() } diff --git a/nostrdb/src/bolt11/bolt11.c b/nostrdb/src/bolt11/bolt11.c @@ -133,7 +133,8 @@ static struct bolt11 *decode_fail(struct bolt11 *b11, char **fail, va_list ap; va_start(ap, fmt); - *fail = tal_vfmt(tal_parent(b11), fmt, ap); + if (fail) + *fail = tal_vfmt(tal_parent(b11), fmt, ap); va_end(ap); return tal_free(b11); } diff --git a/nostrdb/src/nostr_bech32.c b/nostrdb/src/nostr_bech32.c @@ -5,8 +5,8 @@ // Created by William Casarin on 2023-04-09. // -#include "nostr_bech32.h" #include <stdlib.h> +#include "nostr_bech32.h" #include "cursor.h" #include "str_block.h" #include "ccan/endian/endian.h" @@ -151,12 +151,12 @@ static int parse_nostr_bech32_nevent(struct cursor *cur, struct bech32_nevent *n static int parse_nostr_bech32_naddr(struct cursor *cur, struct bech32_naddr *naddr) { struct nostr_tlv tlv; - int i; + int i, has_kind; + has_kind = 0; naddr->identifier.str = NULL; naddr->identifier.len = 0; naddr->pubkey = NULL; - naddr->has_kind = 0; naddr->relays.num_relays = 0; for (i = 0; i < MAX_TLVS; i++) { @@ -183,7 +183,7 @@ static int parse_nostr_bech32_naddr(struct cursor *cur, struct bech32_naddr *nad } } - return naddr->identifier.str != NULL; + return naddr->identifier.str != NULL && has_kind; } static int parse_nostr_bech32_nprofile(struct cursor *cur, struct bech32_nprofile *nprofile) { diff --git a/nostrdb/src/nostrdb.h b/nostrdb/src/nostrdb.h @@ -384,7 +384,6 @@ struct bech32_naddr { struct ndb_str_block identifier; const unsigned char *pubkey; uint32_t kind; - int has_kind; }; struct bech32_nrelay {