diff --git a/Design_de.md b/Design_de.md new file mode 100644 index 0000000..dfb19fb --- /dev/null +++ b/Design_de.md @@ -0,0 +1,129 @@ +# Gedanken und Vorüberlegungen zu libpEpDatatype + +## Anforderungen / Designziele + +* Soll die von der pEpEngine definierten Datentypen so kapseln/wrappen, + dass sie in C++ bequem verwendet werden können, incl. der in C++ üblichen + Eigenschaften (RAII, exception safety, Implementierung der in der stdlib + üblichen Operatoren und (Member-)Funktionen etc.). + +* Die Wrapper sollen zusätzliche semantische Constraints der Daten sicherstellen + bzw. schaffen, z.B. dass alle Strings "NFC-normalisierte UTF-8"-Strings sind. + Ggf. sind die Daten dafür umzukopieren. + +* Die Datentypen sollen im Zusammenspiel mit den Funktionen der public API + der pEpEngine benutzbar sein (entweder _direkt_, z.B. über geeignete + Konvertierungs-Operatoren oder über einfache, verständliche Access-Funktionen). + +* Die Wrapper sollen effizient sein, in Bezug auf Speicherverbrauch und Laufzeitverhalten. + +* libpEpDatatypes soll leicht erweiterbar sein für zukünftige Datentypen. + + +## Implementierungs-Varianten / -Alternativen + +* thin wrapper vs. fat wrapper +* hangeschriebener Wrapper-Code vs. Template-Magic vs. externer Codegenerierung (YML2?) + +Codebeispiele anhand von +* `pEp_identity` – als einfacher Struct Typ +* `identity_list` – als typischer Vertreter der in der Engine zahlreich + vorhandenen (einfach) verketteten Listen. + + +### Thin Wrapper + +* Enthält den C-Datentyp als Data Member +* Erlaubt Zugriff auf die Member des C-Datentyps via `operator->()` +* (+) Code lässt sich leicht generieren, oder mit Unterstützung weniger Hilfsfunktionen per Template generieren +* (+) ist i.A. sehr effizient, erfordert aber ggf. Zusatzfunktionen, um unnötige doppelte + Stringkopien zu vermeiden +* (-) Der aufgerufene C-Code kann die gewrappten Daten – unbemerkt von libpEpDatatype – modifizieren, + so dass z.B. die Zusicherung nach NFC-normalisierten Strings nicht mehr gehalten werden kann. + +``` + class pEp::Wrapper + { + public: + // TODO: Soll "Ownership" von `*id` übernommen werden oder eine Kopie gemacht werden? + // TODO 2: 'explicit', um ungewollte Typumwandlungen zu vermeiden? + Identity(pEp_identity* id); + + // Ruft intern new_identity() mit den 4 Parametern auf. + // Die anderen Member muss man danach manuell setzen. :-| + // Problem: NFC-normalisierung muss vorher erfolgen -> ggf. String-Kopie. + // new_identity() kopiert die Strings dann erneut -> unnötig. + // ad-hoc-Lösung von Volker (2021-10-02): identity_dup_NFC(). Analog könnte man new_identity_NFC() hinzufügen. HACK? + // Evtl. wäre ein new_identity_move_ownership() oder so sinnvoller, da generischer? + Identity(const char* address, const char* fpr, const char* user_id, const char* username); + + // TODO: Rückgabe von wrapped_value, als "const" oder eine Kopie? + pEp_identity* get(); + + // alternative Syntax: Konvertierungs-Operator? + // TODO: automatisch oder 'explicit', um ungewollte Typumwandlungen zu vermeiden? + operator pEp_identity*(); + + private: + pEp_identity* wrapped_value; + }; + + using Identity = Wrapper; +``` + +Beispielcode für die Verwendung: + +``` + pEp::Identity id("example@pep.me", "0xCAFEBABE", "example", "Elon Xaver Ample"); + ... + // je nach o.g. API: + update_identity(session, id); + // oder + update_identity(session, id.get()); +``` + +PROBLEM: Die Funktion ändert die Member von 'id' in einer nicht von libpEpDataType +erkennbaren Weise. UTF8-NFC _kann_ danach _nicht_ mehr garantiert werden. +Der _derzeitige_ Code von update_identity() scheint diesbezüglich sauber zu sein. + +Aber z.B. `message->opt_fields` wird an zahlreichen Stellen modifiziert, NFC-konformität ist +danach nicht mehr gewährleistet. + +Mögliche Lösung: `Wrapper::get()` gibt nicht den C-Datentyp zurück, sondern ein Proxy-Objekt, +in dessen Destruktor alle Member-Strings auf NFC-konformität geprüft werden. +-> Wie zuverlässig ist das? + + +### Thin Wrapper für Listen + +* (+) Code kann generiert werden, auch mit C++ Template Magie +* (+) Sehr effizient, dank Move-Semantik + +* (-) Keine C++-Semantik bezügl. Const-Correctness + d.h. C-Code könnte die Member einer `const pEp::IdentityList` verändern und auch Elemente + hinzufügen/entfernen, was der C++-Semantik von `const` widerspricht. + +* public API der Engine reicht nicht aus, um komplette Schnittstelle von `std::forward_list` + zu implementieren. TODO: API erweitern? Oder "hacken" und in den Interna der Datentypen direkt + herumfummeln? Welche Methoden, Typen und Eigenschaften von `std::forward_list` werden + überhaupt benötigt? + + +### Fat Wrapper + +* Enthält eigene Memberdefinitionen. Möglich wären C-Strings oder C++-Strings. +* .get() generiert C-Datentyp und on-the-fly und gibt ihn zurück. Problem: Ownership? +* (+) Zustand von gewrappten Daten bleibt konsistent (z.B. NFC-konformität, 'const' Correctness etc.) +* (+) Container-API einfacher implementierbar und leistungsfähiger, da sie z.B. + an `std::deque` statt `std::forward_list` angelehnt sein könnte. +* (-) Laufzeit- und Speicheraufwand +* (--) C-Funktionen mit In-Out-Parametern sind so nicht nutzbar. TODO: Vielleicht mit Proxy-Objekten? + +### "Hybrider" Wrapper für Listen +* Enthält den C-Datentyp (verkettete Liste) +* Zusätzlich einen C++-Container (z.B. `std::deque<>`) mit Zeigern/Referenzen auf die Listenelemente +* (+) bietet effizienteren und komfortableren Zugriff von C++ aus als von C aus +* (-) Implementierungsaufwand +* (-) fragil und versagt, wenn C-Code an der C-Liste herumfummelt + +(to be continued)