A C++ wrapper for the basic C datatypes defined by the pEpEngine.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

5.4 KiB

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<pEp_identity*>
	{
	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<pEp_identity*>;

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<T*>::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<T> zu implementieren. TODO: API erweitern? Oder "hacken" und in den Interna der Datentypen direkt herumfummeln? Welche Methoden, Typen und Eigenschaften von std::forward_list<T> 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<T> statt std::forward_list<T> 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)