Implement Codable for NdbNote
Makes it easier to work with other Swift types Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -143,8 +143,16 @@ struct ReplaceableParam: TagConvertible {
|
|||||||
var keychar: AsciiCharacter { "d" }
|
var keychar: AsciiCharacter { "d" }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Signature: Hashable, Equatable {
|
struct Signature: Codable, Hashable, Equatable {
|
||||||
let data: Data
|
let data: Data
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
self.init(try hex_decoder(decoder, expected_len: 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
try hex_encoder(to: encoder, data: self.data)
|
||||||
|
}
|
||||||
|
|
||||||
init(_ p: Data) {
|
init(_ p: Data) {
|
||||||
self.data = p
|
self.data = p
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ enum NdbData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NdbNote: Encodable, Equatable, Hashable {
|
class NdbNote: Codable, Equatable, Hashable {
|
||||||
// we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional
|
// we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional
|
||||||
let owned: Bool
|
private(set) var owned: Bool
|
||||||
let count: Int
|
let count: Int
|
||||||
let key: NoteKey?
|
let key: NoteKey?
|
||||||
let note: UnsafeMutablePointer<ndb_note>
|
let note: UnsafeMutablePointer<ndb_note>
|
||||||
@@ -72,7 +72,6 @@ class NdbNote: Encodable, Equatable, Hashable {
|
|||||||
print("\(NdbNote.notes_created) ndb_notes, \(NdbNote.total_ndb_size) bytes")
|
print("\(NdbNote.notes_created) ndb_notes, \(NdbNote.total_ndb_size) bytes")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func to_owned() -> NdbNote {
|
func to_owned() -> NdbNote {
|
||||||
@@ -86,6 +85,10 @@ class NdbNote: Encodable, Equatable, Hashable {
|
|||||||
|
|
||||||
return NdbNote(note: new_note, size: self.count, owned: true, key: self.key)
|
return NdbNote(note: new_note, size: self.count, owned: true, key: self.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mark_ownership_moved() {
|
||||||
|
self.owned = false
|
||||||
|
}
|
||||||
|
|
||||||
var content: String {
|
var content: String {
|
||||||
String(cString: content_raw, encoding: .utf8) ?? ""
|
String(cString: content_raw, encoding: .utf8) ?? ""
|
||||||
@@ -161,13 +164,63 @@ class NdbNote: Encodable, Equatable, Hashable {
|
|||||||
try container.encode(content, forKey: .content)
|
try container.encode(content, forKey: .content)
|
||||||
try container.encode(tags, forKey: .tags)
|
try container.encode(tags, forKey: .tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init(from decoder: any Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
let content = try container.decode(String.self, forKey: .content)
|
||||||
|
let pubkey = try container.decode(Pubkey.self, forKey: .pubkey)
|
||||||
|
let kind = try container.decode(UInt32.self, forKey: .kind)
|
||||||
|
let tags = try container.decode([[String]].self, forKey: .tags)
|
||||||
|
let createdAt = try container.decode(UInt32.self, forKey: .created_at)
|
||||||
|
let noteId = try container.decode(NoteId.self, forKey: .id)
|
||||||
|
let signature = try container.decode(Signature.self, forKey: .sig)
|
||||||
|
|
||||||
|
guard let note = NdbNote.init(content: content, author: pubkey, kind: kind, tags: tags, createdAt: createdAt, id: noteId, sig: signature) else {
|
||||||
|
throw DecodingError.initializationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
self.note = note.note
|
||||||
|
self.owned = note.owned
|
||||||
|
note.mark_ownership_moved() // This is done to prevent a double-free error when both `self` and `note` get deinitialized.
|
||||||
|
self.count = note.count
|
||||||
|
self.key = note.key
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DecodingError: Error {
|
||||||
|
case initializationFailed
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG_NOTE_SIZE
|
#if DEBUG_NOTE_SIZE
|
||||||
static var total_ndb_size: Int = 0
|
static var total_ndb_size: Int = 0
|
||||||
static var notes_created: Int = 0
|
static var notes_created: Int = 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
fileprivate enum NoteConstructionMaterial {
|
||||||
|
case keypair(Keypair)
|
||||||
|
case manual(Pubkey, Signature, NoteId)
|
||||||
|
|
||||||
|
var pubkey: Pubkey {
|
||||||
|
switch self {
|
||||||
|
case .keypair(let keypair):
|
||||||
|
return keypair.pubkey
|
||||||
|
case .manual(let pubkey, _, _):
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var privkey: Privkey? {
|
||||||
|
switch self {
|
||||||
|
case .keypair(let kp):
|
||||||
|
return kp.privkey
|
||||||
|
case .manual(_, _, _):
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate init?(content: String, noteConstructionMaterial: NoteConstructionMaterial, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
||||||
|
|
||||||
var builder = ndb_builder()
|
var builder = ndb_builder()
|
||||||
let buflen = MAX_NOTE_SIZE
|
let buflen = MAX_NOTE_SIZE
|
||||||
@@ -175,7 +228,7 @@ class NdbNote: Encodable, Equatable, Hashable {
|
|||||||
|
|
||||||
ndb_builder_init(&builder, buf, Int32(buflen))
|
ndb_builder_init(&builder, buf, Int32(buflen))
|
||||||
|
|
||||||
var pk_raw = keypair.pubkey.bytes
|
var pk_raw = noteConstructionMaterial.pubkey.bytes
|
||||||
|
|
||||||
ndb_builder_set_pubkey(&builder, &pk_raw)
|
ndb_builder_set_pubkey(&builder, &pk_raw)
|
||||||
ndb_builder_set_kind(&builder, UInt32(kind))
|
ndb_builder_set_kind(&builder, UInt32(kind))
|
||||||
@@ -203,30 +256,57 @@ class NdbNote: Encodable, Equatable, Hashable {
|
|||||||
|
|
||||||
var n = UnsafeMutablePointer<ndb_note>?(nil)
|
var n = UnsafeMutablePointer<ndb_note>?(nil)
|
||||||
|
|
||||||
|
|
||||||
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
|
var len: Int32 = 0
|
||||||
if var the_kp {
|
|
||||||
len = ndb_builder_finalize(&builder, &n, &the_kp)
|
|
||||||
} else {
|
|
||||||
len = ndb_builder_finalize(&builder, &n, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len <= 0 {
|
switch noteConstructionMaterial {
|
||||||
free(buf)
|
case .keypair(let keypair):
|
||||||
return nil
|
var the_kp: ndb_keypair? = nil
|
||||||
|
|
||||||
|
if let sec = noteConstructionMaterial.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if var the_kp {
|
||||||
|
len = ndb_builder_finalize(&builder, &n, &the_kp)
|
||||||
|
} else {
|
||||||
|
len = ndb_builder_finalize(&builder, &n, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len <= 0 {
|
||||||
|
free(buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case .manual(_, let signature, _):
|
||||||
|
var raw_sig = signature.data.bytes
|
||||||
|
ndb_builder_set_sig(&builder, &raw_sig)
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Finalize note, save length, and ensure it is higher than zero (which signals finalization has succeeded)
|
||||||
|
len = ndb_builder_finalize(&builder, &n, nil)
|
||||||
|
guard len > 0 else { throw InitError.generic }
|
||||||
|
|
||||||
|
let scratch_buf_len = MAX_NOTE_SIZE
|
||||||
|
let scratch_buf = malloc(scratch_buf_len)
|
||||||
|
defer { free(scratch_buf) } // Ensure we deallocate as soon as we leave this scope, regardless of the outcome
|
||||||
|
|
||||||
|
// Calculate the ID based on the content
|
||||||
|
guard ndb_calculate_id(n, scratch_buf, Int32(scratch_buf_len)) == 1 else { throw InitError.generic }
|
||||||
|
|
||||||
|
// Verify the signature against the pubkey and the computed ID, to verify the validity of the whole note
|
||||||
|
var ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY))
|
||||||
|
guard ndb_note_verify(&ctx, ndb_note_pubkey(n), ndb_note_id(n), ndb_note_sig(n)) == 1 else { throw InitError.generic }
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
free(buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//guard let n else { return nil }
|
//guard let n else { return nil }
|
||||||
@@ -244,6 +324,14 @@ class NdbNote: Encodable, Equatable, Hashable {
|
|||||||
self.key = nil
|
self.key = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convenience init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
||||||
|
self.init(content: content, noteConstructionMaterial: .keypair(keypair), kind: kind, tags: tags, createdAt: createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init?(content: String, author: Pubkey, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970), id: NoteId, sig: Signature) {
|
||||||
|
self.init(content: content, noteConstructionMaterial: .manual(author, sig, id), kind: kind, tags: tags, createdAt: createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? {
|
static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? {
|
||||||
return json.withCString { cstr in
|
return json.withCString { cstr in
|
||||||
return NdbNote.owned_from_json_cstr(
|
return NdbNote.owned_from_json_cstr(
|
||||||
@@ -520,3 +608,10 @@ func hex_encode(_ data: Data) -> String {
|
|||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NdbNote {
|
||||||
|
/// A generic init error type to help make error handling code more concise
|
||||||
|
fileprivate enum InitError: Error {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user