Vertrauenswürdige API-Referenz

Trusty stellt APIs für die Entwicklung von zwei Klassen von Anwendungen/Diensten bereit:

  • Vertrauenswürdige Anwendungen oder Dienste, die auf dem TEE-Prozessor ausgeführt werden
  • Normale/nicht vertrauenswürdige Anwendungen, die auf dem Hauptprozessor ausgeführt werden und die von vertrauenswürdigen Anwendungen bereitgestellten Dienste nutzen

Die Trusty API beschreibt im Allgemeinen das Trusty Inter-Process Communication (IPC)-System, einschließlich der Kommunikation mit der nicht sicheren Welt. Software, die auf dem Hauptprozessor läuft, kann Trusty APIs verwenden, um eine Verbindung zu vertrauenswürdigen Anwendungen/Diensten herzustellen und beliebige Nachrichten mit ihnen auszutauschen, genau wie ein Netzwerkdienst über IP. Es liegt an der Anwendung, das Datenformat und die Semantik dieser Nachrichten mithilfe eines Protokolls auf App-Ebene zu bestimmen. Die zuverlässige Zustellung von Nachrichten wird durch die zugrunde liegende Trusty-Infrastruktur (in Form von Treibern, die auf dem Hauptprozessor ausgeführt werden) gewährleistet und die Kommunikation erfolgt vollständig asynchron.

Häfen und Kanäle

Ports werden von Trusty-Anwendungen verwendet, um Dienstendpunkte in Form eines benannten Pfads bereitzustellen, mit dem Clients eine Verbindung herstellen. Dadurch erhalten Clients eine einfache, stringbasierte Dienst-ID zur Verwendung. Die Namenskonvention ist die Benennung im Reverse-DNS-Stil, z. B. com.google.servicename .

Wenn ein Client eine Verbindung zu einem Port herstellt, erhält der Client einen Kanal für die Interaktion mit einem Dienst. Der Dienst muss eine eingehende Verbindung akzeptieren und erhält dann auch einen Kanal. Im Wesentlichen werden Ports verwendet, um nach Diensten zu suchen, und die Kommunikation erfolgt dann über ein Paar verbundener Kanäle (dh Verbindungsinstanzen auf einem Port). Wenn ein Client eine Verbindung zu einem Port herstellt, wird eine symmetrische, bidirektionale Verbindung hergestellt. Über diesen Vollduplex-Pfad können Clients und Server beliebige Nachrichten austauschen, bis eine Seite beschließt, die Verbindung abzubrechen.

Nur vertrauenswürdige Anwendungen auf der sicheren Seite oder vertrauenswürdige Kernelmodule können Ports erstellen. Anwendungen, die auf der nicht sicheren Seite (in der normalen Welt) ausgeführt werden, können nur eine Verbindung zu Diensten herstellen, die von der sicheren Seite veröffentlicht werden.

Je nach Anforderung kann eine vertrauenswürdige Anwendung gleichzeitig Client und Server sein. Eine vertrauenswürdige Anwendung, die einen Dienst veröffentlicht (als Server), muss möglicherweise eine Verbindung zu anderen Diensten (als Client) herstellen.

Behandeln Sie die API

Handles sind vorzeichenlose Ganzzahlen, die Ressourcen wie Ports und Kanäle darstellen, ähnlich wie Dateideskriptoren in UNIX. Nachdem Handles erstellt wurden, werden sie in einer anwendungsspezifischen Handle-Tabelle platziert und können später referenziert werden.

Ein Aufrufer kann private Daten mit einem Handle verknüpfen, indem er die Methode set_cookie() verwendet.

Methoden in der Handle-API

Handles sind nur im Kontext einer Anwendung gültig. Eine Anwendung sollte den Wert eines Handles nicht an andere Anwendungen weitergeben, es sei denn, dies wird ausdrücklich angegeben. Ein Handle-Wert sollte nur durch Vergleich mit der INVALID_IPC_HANDLE #define, die eine Anwendung als Hinweis darauf verwenden kann, dass ein Handle ungültig oder nicht gesetzt ist.

Ordnet die vom Anrufer bereitgestellten privaten Daten einem angegebenen Handle zu.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : Jedes Handle, das von einem der API-Aufrufe zurückgegeben wird

[in] cookie : Zeiger auf beliebige User-Space-Daten in der Trusty-Anwendung

[retval]: NO_ERROR bei Erfolg, ansonsten < 0 Fehlercode

Dieser Aufruf ist nützlich für die Behandlung von Ereignissen, wenn diese zu einem späteren Zeitpunkt nach der Erstellung des Handles auftreten. Der Ereignisverarbeitungsmechanismus liefert das Handle und sein Cookie an den Ereignishandler zurück.

Mithilfe des Aufrufs wait() kann auf Handles auf Ereignisse gewartet werden.

Warten()

Wartet für einen bestimmten Zeitraum darauf, dass ein Ereignis an einem bestimmten Handle auftritt.

long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)

[in] handle_id : Jedes Handle, das von einem der API-Aufrufe zurückgegeben wird

[out] event : Ein Zeiger auf die Struktur, die ein Ereignis darstellt, das an diesem Handle aufgetreten ist

[in] timeout_msecs : Ein Timeout-Wert in Millisekunden; Ein Wert von -1 ist ein unendliches Timeout

[retval]: NO_ERROR , wenn innerhalb eines angegebenen Timeout-Intervalls ein gültiges Ereignis aufgetreten ist; ERR_TIMED_OUT , wenn ein angegebenes Timeout abgelaufen ist, aber kein Ereignis aufgetreten ist; < 0 für andere Fehler

Bei Erfolg ( retval == NO_ERROR ) füllt der Aufruf wait() eine angegebene uevent_t Struktur mit Informationen über das aufgetretene Ereignis.

typedef struct uevent {
    uint32_t handle; /* handle this event is related to */
    uint32_t event;  /* combination of IPC_HANDLE_POLL_XXX flags */
    void    *cookie; /* cookie associated with this handle */
} uevent_t;

Das event enthält eine Kombination der folgenden Werte:

enum {
  IPC_HANDLE_POLL_NONE    = 0x0,
  IPC_HANDLE_POLL_READY   = 0x1,
  IPC_HANDLE_POLL_ERROR   = 0x2,
  IPC_HANDLE_POLL_HUP     = 0x4,
  IPC_HANDLE_POLL_MSG     = 0x8,
  IPC_HANDLE_POLL_SEND_UNBLOCKED = 0x10,
  … more values[TBD]
};

IPC_HANDLE_POLL_NONE – es stehen tatsächlich keine Ereignisse an, der Anrufer sollte die Wartezeit neu starten

IPC_HANDLE_POLL_ERROR – ein nicht spezifizierter interner Fehler ist aufgetreten

IPC_HANDLE_POLL_READY – hängt wie folgt vom Handle-Typ ab:

  • Bei Ports gibt dieser Wert an, dass eine Verbindung aussteht
  • Bei Kanälen gibt dieser Wert an, dass eine asynchrone Verbindung (siehe connect() ) hergestellt wurde

Die folgenden Ereignisse sind nur für Kanäle relevant:

  • IPC_HANDLE_POLL_HUP – zeigt an, dass ein Kanal von einem Peer geschlossen wurde
  • IPC_HANDLE_POLL_MSG – zeigt an, dass für diesen Kanal eine Nachricht aussteht
  • IPC_HANDLE_POLL_SEND_UNBLOCKED – zeigt an, dass ein zuvor blockierter Anrufer möglicherweise erneut versucht, eine Nachricht zu senden (Einzelheiten finden Sie in der Beschreibung von send_msg() ).

Ein Ereignishandler sollte darauf vorbereitet sein, eine Kombination bestimmter Ereignisse zu verarbeiten, da möglicherweise mehrere Bits gleichzeitig gesetzt sind. Für einen Kanal ist es beispielsweise möglich, dass gleichzeitig Nachrichten ausstehen und eine Verbindung von einem Peer geschlossen wird.

Die meisten Ereignisse sind klebrig. Sie bleiben bestehen, solange die zugrunde liegende Bedingung besteht (z. B. werden alle ausstehenden Nachrichten empfangen und ausstehende Verbindungsanfragen bearbeitet). Eine Ausnahme bildet das Ereignis IPC_HANDLE_POLL_SEND_UNBLOCKED , das bei einem Lesevorgang gelöscht wird und die Anwendung nur eine Chance hat, es zu verarbeiten.

Handles können durch Aufruf der Methode close() zerstört werden.

schließen()

Zerstört die dem angegebenen Handle zugeordnete Ressource und entfernt sie aus der Handle-Tabelle.

long close(uint32_t handle_id);

[in] handle_id : Zu zerstörendes Handle

[retval]: 0 bei Erfolg; andernfalls ein negativer Fehler

Server-API

Ein Server erstellt zunächst einen oder mehrere benannte Ports, die seine Dienstendpunkte darstellen. Jeder Port wird durch ein Handle dargestellt.

Methoden in der Server-API

port_create()

Erstellt einen benannten Service-Port.

long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size,
uint32_t flags)

[in] path : Der String-Name des Ports (wie oben beschrieben). Dieser Name sollte im gesamten System eindeutig sein; Versuche, ein Duplikat zu erstellen, schlagen fehl.

[in] num_recv_bufs : Die maximale Anzahl an Puffern, die ein Kanal an diesem Port vorab zuweisen kann, um den Datenaustausch mit dem Client zu erleichtern. Puffer werden für Daten, die in beide Richtungen gesendet werden, separat gezählt. Wenn Sie hier also 1 angeben, bedeutet dies, dass 1 Sende- und 1 Empfangspuffer vorab zugewiesen sind. Im Allgemeinen hängt die Anzahl der erforderlichen Puffer von der übergeordneten Protokollvereinbarung zwischen Client und Server ab. Bei einem sehr synchronen Protokoll (Nachricht senden, Antwort empfangen, bevor eine weitere gesendet wird) kann die Zahl bis zu 1 betragen. Die Anzahl kann jedoch höher sein, wenn der Client erwartet, mehr als eine Nachricht zu senden, bevor eine Antwort erscheinen kann (z. B. eine Nachricht als Prolog und eine andere als eigentlicher Befehl). Die zugewiesenen Puffersätze gelten pro Kanal, sodass zwei separate Verbindungen (Kanäle) separate Puffersätze hätten.

[in] recv_buf_size : Maximale Größe jedes einzelnen Puffers im obigen Puffersatz. Dieser Wert ist protokollabhängig und begrenzt effektiv die maximale Nachrichtengröße, die Sie mit dem Peer austauschen können

[in] flags : Eine Kombination von Flags, die zusätzliches Portverhalten angeben

Dieser Wert sollte eine Kombination der folgenden Werte sein:

IPC_PORT_ALLOW_TA_CONNECT – ermöglicht eine Verbindung von anderen sicheren Apps

IPC_PORT_ALLOW_NS_CONNECT – ermöglicht eine Verbindung aus der nicht sicheren Welt

[retval]: Handle für den erstellten Port, wenn nicht negativ, oder ein bestimmter Fehler, wenn negativ

Der Server fragt dann die Liste der Port-Handles für eingehende Verbindungen mit dem Aufruf wait() ab. Beim Empfang einer Verbindungsanforderung, die durch das im event der uevent_t Struktur gesetzte Bit IPC_HANDLE_POLL_READY angezeigt wird, sollte der Server accept() aufrufen, um den Verbindungsaufbau abzuschließen und einen Kanal (dargestellt durch ein anderes Handle) zu erstellen, der dann nach eingehenden Nachrichten abgefragt werden kann .

akzeptieren()

Akzeptiert eine eingehende Verbindung und erhält ein Handle für einen Kanal.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id : Handle, das den Port darstellt, mit dem ein Client eine Verbindung hergestellt hat

[out] peer_uuid : Zeiger auf eine uuid_t Struktur, die mit der UUID der verbindenden Clientanwendung gefüllt werden soll. Es wird ausschließlich auf Nullen gesetzt, wenn die Verbindung aus der nicht sicheren Welt stammt

[retval]: Handle für einen Kanal (falls nicht negativ), auf dem der Server Nachrichten mit dem Client austauschen kann (oder andernfalls ein Fehlercode)

Client-API

Dieser Abschnitt enthält die Methoden in der Client-API.

Methoden in der Client-API

verbinden()

Initiiert eine Verbindung zu einem namentlich angegebenen Port.

long connect(const char *path, uint flags);

[in] path : Name eines von einer Trusty-Anwendung veröffentlichten Ports

[in] flags : Gibt zusätzliches, optionales Verhalten an

[retval]: Handle für einen Kanal, über den Nachrichten mit dem Server ausgetauscht werden können; Fehler, wenn negativ

Wenn keine flags angegeben sind (der flags Parameter ist auf 0 gesetzt), initiiert der Aufruf von connect() eine synchrone Verbindung zu einem angegebenen Port, die sofort einen Fehler zurückgibt, wenn der Port nicht existiert, und einen Block erstellt, bis der Server andernfalls eine Verbindung akzeptiert .

Dieses Verhalten kann durch Angabe einer Kombination aus zwei Werten geändert werden, wie unten beschrieben:

enum {
IPC_CONNECT_WAIT_FOR_PORT = 0x1,
IPC_CONNECT_ASYNC = 0x2,
};

IPC_CONNECT_WAIT_FOR_PORT – erzwingt, dass ein connect() -Aufruf wartet, wenn der angegebene Port bei der Ausführung nicht sofort vorhanden ist, anstatt sofort fehlzuschlagen.

IPC_CONNECT_ASYNC – wenn festgelegt, initiiert es eine asynchrone Verbindung. Eine Anwendung muss das zurückgegebene Handle abfragen (durch Aufrufen von wait() für ein Verbindungsabschlussereignis, das durch das im Ereignisfeld der uevent_t Struktur festgelegte Bit IPC_HANDLE_POLL_READY angezeigt wird, bevor der normale Betrieb gestartet wird).

Messaging-API

Die Messaging-API-Aufrufe ermöglichen das Senden und Lesen von Nachrichten über eine zuvor aufgebaute Verbindung (Kanal). Die Messaging-API-Aufrufe sind für Server und Clients gleich.

Ein Client erhält ein Handle für einen Kanal, indem er einen connect() Aufruf ausgibt, und ein Server erhält ein Kanalhandle von einem accept() Aufruf, wie oben beschrieben.

Struktur einer Trusty-Nachricht

Wie im Folgenden gezeigt, haben die von der Trusty-API ausgetauschten Nachrichten eine minimale Struktur, so dass es dem Server und dem Client überlassen bleibt, sich auf die Semantik des tatsächlichen Inhalts zu einigen:

/*
 *  IPC message
 */
typedef struct iovec {
        void   *base;
        size_t  len;
} iovec_t;

typedef struct ipc_msg {
        uint     num_iov; /* number of iovs in this message */
        iovec_t  *iov;    /* pointer to iov array */

        uint     num_handles; /* reserved, currently not supported */
        handle_t *handles;    /* reserved, currently not supported */
} ipc_msg_t;

Eine Nachricht kann aus einem oder mehreren nicht zusammenhängenden Puffern bestehen, die durch ein Array von iovec_t Strukturen dargestellt werden. Trusty führt mithilfe des iov Arrays Scatter-Gather-Lese- und Schreibvorgänge für diese Blöcke durch. Der Inhalt der Puffer, die durch das iov Array beschrieben werden können, ist völlig willkürlich.

Methoden in der Messaging-API

send_msg()

Sendet eine Nachricht über einen angegebenen Kanal.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : Handle für den Kanal, über den die Nachricht gesendet werden soll

[in] msg : Zeiger auf die ipc_msg_t structure , die die Nachricht beschreibt

[retval]: Gesamtzahl der bei Erfolg gesendeten Bytes; andernfalls ein negativer Fehler

Wenn der Client (oder Server) versucht, eine Nachricht über den Kanal zu senden und in der Ziel-Peer-Nachrichtenwarteschlange kein Platz vorhanden ist, wechselt der Kanal möglicherweise in den Status „Sendeblockiert“ (dies sollte bei einem einfachen synchronen Anforderungs-/Antwortprotokoll niemals passieren). kann aber in komplizierteren Fällen vorkommen), was durch die Rückgabe eines ERR_NOT_ENOUGH_BUFFER Fehlercodes angezeigt wird. In einem solchen Fall muss der Aufrufer warten, bis der Peer etwas Platz in seiner Empfangswarteschlange freigibt, indem er die verarbeiteten und zurückgezogenen Nachrichten abruft. Dies wird durch das IPC_HANDLE_POLL_SEND_UNBLOCKED -Bit angezeigt, das im event der vom Aufruf von wait() zurückgegebenen Struktur uevent_t gesetzt ist.

get_msg()

Ruft Metainformationen über die nächste Nachricht in einer Warteschlange für eingehende Nachrichten ab

eines bestimmten Kanals.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : Handle des Kanals, auf dem eine neue Nachricht abgerufen werden muss

[out] msg_info : Nachrichteninformationsstruktur, die wie folgt beschrieben wird:

typedef struct ipc_msg_info {
        size_t    len;  /* total message length */
        uint32_t  id;   /* message id */
} ipc_msg_info_t;

Jeder Nachricht wird im gesamten Satz ausstehender Nachrichten eine eindeutige ID zugewiesen, und die Gesamtlänge jeder Nachricht wird eingetragen. Sofern konfiguriert und vom Protokoll zugelassen, können für einen bestimmten Kanal mehrere ausstehende (geöffnete) Nachrichten gleichzeitig vorhanden sein.

[retval]: NO_ERROR bei Erfolg; andernfalls ein negativer Fehler

read_msg()

Liest den Inhalt der Nachricht mit der angegebenen ID ab dem angegebenen Offset.

long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t
*msg);

[in] handle : Handle des Kanals, von dem aus die Nachricht gelesen werden soll

[in] msg_id : ID der zu lesenden Nachricht

[in] offset : Offset in der Nachricht, ab dem mit dem Lesen begonnen werden soll

[out] msg : Zeiger auf die ipc_msg_t Struktur, die eine Reihe von Puffern beschreibt, in denen eingehende Nachrichtendaten gespeichert werden sollen

[retval]: Gesamtzahl der Bytes, die bei Erfolg in den msg gespeichert werden; andernfalls ein negativer Fehler

Die Methode read_msg kann je nach Bedarf mehrmals aufgerufen werden, beginnend mit einem anderen (nicht unbedingt sequenziellen) Offset.

put_msg()

Zieht eine Nachricht mit einer angegebenen ID zurück.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Handle des Kanals, auf dem die Nachricht angekommen ist

[in] msg_id : ID der Nachricht, die zurückgezogen wird

[retval]: NO_ERROR bei Erfolg; andernfalls ein negativer Fehler

Auf den Nachrichteninhalt kann nicht zugegriffen werden, nachdem eine Nachricht zurückgezogen und der von ihr belegte Puffer freigegeben wurde.

Dateideskriptor-API

Die Dateideskriptor-API umfasst die Aufrufe read() , write() und ioctl() . Alle diese Aufrufe können mit einem vordefinierten (statischen) Satz von Dateideskriptoren arbeiten, die traditionell durch kleine Zahlen dargestellt werden. In der aktuellen Implementierung ist der Dateideskriptorraum vom IPC-Handleraum getrennt. Die Dateideskriptor-API in Trusty ähnelt einer herkömmlichen, auf Dateideskriptoren basierenden API.

Standardmäßig gibt es drei vordefinierte (Standard- und bekannte) Dateideskriptoren:

  • 0 - Standardeingabe. Die Standardimplementierung der Standardeingabe fd ist eine No-Op-Implementierung (da von vertrauenswürdigen Anwendungen keine interaktive Konsole erwartet wird), daher sollte das Lesen, Schreiben oder Aufrufen ioctl() auf fd 0 einen Fehler ERR_NOT_SUPPORTED zurückgeben.
  • 1 - Standardausgabe. Auf die Standardausgabe geschriebene Daten können (je nach LK-Debug-Level) an UART und/oder ein auf der nicht sicheren Seite verfügbares Speicherprotokoll weitergeleitet werden, je nach Plattform und Konfiguration. Unkritische Debugprotokolle und -meldungen sollten in der Standardausgabe angezeigt werden. Die Methoden read() und ioctl() sind no-ops und sollten einen ERR_NOT_SUPPORTED -Fehler zurückgeben.
  • 2 - Standardfehler. In den Standardfehler geschriebene Daten sollten je nach Plattform und Konfiguration an den UART oder das Speicherprotokoll weitergeleitet werden, das auf der nicht sicheren Seite verfügbar ist. Es wird empfohlen, nur kritische Nachrichten in den Standardfehler zu schreiben, da dieser Stream mit hoher Wahrscheinlichkeit ungedrosselt ist. Die Methoden read() und ioctl() sind no-ops und sollten einen ERR_NOT_SUPPORTED -Fehler zurückgeben.

Auch wenn dieser Satz von Dateideskriptoren erweitert werden kann, um mehr fds zu implementieren (um plattformspezifische Erweiterungen zu implementieren), muss die Erweiterung von Dateideskriptoren mit Vorsicht erfolgen. Das Erweitern von Dateideskriptoren kann zu Konflikten führen und wird im Allgemeinen nicht empfohlen.

Methoden in der Dateideskriptor-API

lesen()

Versucht, Daten aus einem angegebenen Dateideskriptor bis zur count der Bytes zu lesen.

long read(uint32_t fd, void *buf, uint32_t count);

[in] fd : Dateideskriptor, aus dem gelesen werden soll

[out] buf : Zeiger auf einen Puffer, in dem Daten gespeichert werden sollen

[in] count : Maximale Anzahl der zu lesenden Bytes

[retval]: Zurückgegebene Anzahl der gelesenen Bytes; andernfalls ein negativer Fehler

schreiben()

Schreibt bis zu count von Bytes an Daten in den angegebenen Dateideskriptor.

long write(uint32_t fd, void *buf, uint32_t count);

[in] fd : Dateideskriptor, in den geschrieben werden soll

[out] buf : Zeiger auf die zu schreibenden Daten

[in] count : Maximale Anzahl der zu schreibenden Bytes

[retval]: Zurückgegebene Anzahl geschriebener Bytes; andernfalls ein negativer Fehler

ioctl()

Ruft einen angegebenen ioctl Befehl für einen bestimmten Dateideskriptor auf.

long ioctl(uint32_t fd, uint32_t cmd, void *args);

[in] fd : Dateideskriptor, für den ioctl() aufgerufen werden soll

[in] cmd : Der ioctl Befehl

[in/out] args : Zeiger auf ioctl() -Argumente

Verschiedene API

Methoden in der Miscellaneous API

Zeit bekommen()

Gibt die aktuelle Systemzeit (in Nanosekunden) zurück.

long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);

[in] clock_id : Plattformabhängig; Übergeben Sie Null als Standard

[in] flags : Reserviert, sollte Null sein

[out] time : Zeiger auf einen int64_t Wert, in dem die aktuelle Zeit gespeichert werden soll

[retval]: NO_ERROR bei Erfolg; andernfalls ein negativer Fehler

nanosleep()

Unterbricht die Ausführung der aufrufenden Anwendung für einen bestimmten Zeitraum und setzt sie nach diesem Zeitraum fort.

long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)

[in] clock_id : Reserviert, sollte Null sein

[in] flags : Reserviert, sollte Null sein

[in] sleep_time : Ruhezeit in Nanosekunden

[retval]: NO_ERROR bei Erfolg; andernfalls ein negativer Fehler

Beispiel eines vertrauenswürdigen Anwendungsservers

Die folgende Beispielanwendung zeigt die Verwendung der oben genannten APIs. Das Beispiel erstellt einen „Echo“-Dienst, der mehrere eingehende Verbindungen verarbeitet und alle Nachrichten, die er von Clients empfängt, die von der sicheren oder nicht sicheren Seite stammen, an den Anrufer zurückgibt.

#include <uapi/err.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trusty_ipc.h>
#define LOG_TAG "echo_srv"
#define TLOGE(fmt, ...) \
    fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ##__VA_ARGS__)

# define MAX_ECHO_MSG_SIZE 64

static
const char * srv_name = "com.android.echo.srv.echo";

static uint8_t msg_buf[MAX_ECHO_MSG_SIZE];

/*
 *  Message handler
 */
static int handle_msg(handle_t chan) {
  int rc;
  struct iovec iov;
  ipc_msg_t msg;
  ipc_msg_info_t msg_inf;

  iov.iov_base = msg_buf;
  iov.iov_len = sizeof(msg_buf);

  msg.num_iov = 1;
  msg.iov = &iov;
  msg.num_handles = 0;
  msg.handles = NULL;

  /* get message info */
  rc = get_msg(chan, &msg_inf);
  if (rc == ERR_NO_MSG)
    return NO_ERROR; /* no new messages */

  if (rc != NO_ERROR) {
    TLOGE("failed (%d) to get_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  /* read msg content */
  rc = read_msg(chan, msg_inf.id, 0, &msg);
  if (rc < 0) {
    TLOGE("failed (%d) to read_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  /* update number of bytes received */
  iov.iov_len = (size_t) rc;

  /* send message back to the caller */
  rc = send_msg(chan, &msg);
  if (rc < 0) {
    TLOGE("failed (%d) to send_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  /* retire message */
  rc = put_msg(chan, msg_inf.id);
  if (rc != NO_ERROR) {
    TLOGE("failed (%d) to put_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  return NO_ERROR;
}

/*
 *  Channel event handler
 */
static void handle_channel_event(const uevent_t * ev) {
  int rc;

  if (ev->event & IPC_HANDLE_POLL_MSG) {
    rc = handle_msg(ev->handle);
    if (rc != NO_ERROR) {
      /* report an error and close channel */
      TLOGE("failed (%d) to handle event on channel %d\n",
        rc, ev->handle);
      close(ev->handle);
    }
    return;
  }
  if (ev->event & IPC_HANDLE_POLL_HUP) {
    /* closed by peer. */
    close(ev->handle);
    return;
  }
}

/*
 *  Port event handler
 */
static void handle_port_event(const uevent_t * ev) {
  uuid_t peer_uuid;

  if ((ev->event & IPC_HANDLE_POLL_ERROR) ||
    (ev->event & IPC_HANDLE_POLL_HUP) ||
    (ev->event & IPC_HANDLE_POLL_MSG) ||
    (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) {
    /* should never happen with port handles */
    TLOGE("error event (0x%x) for port (%d)\n",
      ev->event, ev->handle);
    abort();
  }
  if (ev->event & IPC_HANDLE_POLL_READY) {
    /* incoming connection: accept it */
    int rc = accept(ev->handle, &peer_uuid);
    if (rc < 0) {
      TLOGE("failed (%d) to accept on port %d\n",
        rc, ev->handle);
      return;
    }
    handle_t chan = rc;
    while (true){
      struct uevent cev;

      rc = wait(chan, &cev, INFINITE_TIME);
      if (rc < 0) {
        TLOGE("wait returned (%d)\n", rc);
        abort();
      }
      handle_channel_event(&cev);
      if (cev.event & IPC_HANDLE_POLL_HUP) {
        return;
      }
    }
  }
}


/*
 *  Main application entry point
 */
int main(void) {
  int rc;
  handle_t port;

  /* Initialize service */
  rc = port_create(srv_name, 1, MAX_ECHO_MSG_SIZE,
    IPC_PORT_ALLOW_NS_CONNECT |
    IPC_PORT_ALLOW_TA_CONNECT);
  if (rc < 0) {
    TLOGE("Failed (%d) to create port %s\n",
      rc, srv_name);
    abort();
  }
  port = (handle_t) rc;

  /* enter main event loop */
  while (true) {
    uevent_t ev;

    ev.handle = INVALID_IPC_HANDLE;
    ev.event = 0;
    ev.cookie = NULL;

    /* wait forever */
    rc = wait(port, &ev, INFINITE_TIME);
    if (rc == NO_ERROR) {
      /* got an event */
      handle_port_event(&ev);
    } else {
      TLOGE("wait returned (%d)\n", rc);
      abort();
    }
  }
  return 0;
}

Die Methode run_end_to_end_msg_test() sendet 10.000 Nachrichten asynchron an den „echo“-Dienst und verarbeitet Antworten.

static int run_echo_test(void)
{
  int rc;
  handle_t chan;
  uevent_t uevt;
  uint8_t tx_buf[64];
  uint8_t rx_buf[64];
  ipc_msg_info_t inf;
  ipc_msg_t   tx_msg;
  iovec_t     tx_iov;
  ipc_msg_t   rx_msg;
  iovec_t     rx_iov;

  /* prepare tx message buffer */
  tx_iov.base = tx_buf;
  tx_iov.len  = sizeof(tx_buf);
  tx_msg.num_iov = 1;
  tx_msg.iov     = &tx_iov;
  tx_msg.num_handles = 0;
  tx_msg.handles = NULL;

  memset (tx_buf, 0x55, sizeof(tx_buf));

  /* prepare rx message buffer */
  rx_iov.base = rx_buf;
  rx_iov.len  = sizeof(rx_buf);
  rx_msg.num_iov = 1;
  rx_msg.iov     = &rx_iov;
  rx_msg.num_handles = 0;
  rx_msg.handles = NULL;

  /* open connection to echo service */
  rc = sync_connect(srv_name, 1000);
  if(rc < 0)
    return rc;

  /* got channel */
  chan = (handle_t)rc;

  /* send/receive 10000 messages asynchronously. */
  uint tx_cnt = 10000;
  uint rx_cnt = 10000;

  while (tx_cnt || rx_cnt) {
    /* send messages until all buffers are full */
while (tx_cnt) {
    rc = send_msg(chan, &tx_msg);
      if (rc == ERR_NOT_ENOUGH_BUFFER)
      break;  /* no more space */
    if (rc != 64) {
      if (rc > 0) {
        /* incomplete send */
        rc = ERR_NOT_VALID;
}
      goto abort_test;
}
    tx_cnt--;
  }

  /* wait for reply msg or room */
  rc = wait(chan, &uevt, 1000);
  if (rc != NO_ERROR)
    goto abort_test;

  /* drain all messages */
  while (rx_cnt) {
    /* get a reply */
      rc = get_msg(chan, &inf);
    if (rc == ERR_NO_MSG)
        break;  /* no more messages  */
  if (rc != NO_ERROR)
goto abort_test;

  /* read reply data */
    rc = read_msg(chan, inf.id, 0, &rx_msg);
  if (rc != 64) {
    /* unexpected reply length */
    rc = ERR_NOT_VALID;
    goto abort_test;
}

  /* discard reply */
  rc = put_msg(chan, inf.id);
  if (rc != NO_ERROR)
    goto abort_test;
  rx_cnt--;
  }
}

abort_test:
  close(chan);
  return rc;
}

Nicht sichere Welt-APIs und -Anwendungen

Eine Reihe vertrauenswürdiger Dienste, die von der sicheren Seite veröffentlicht und mit dem Attribut IPC_PORT_ALLOW_NS_CONNECT gekennzeichnet sind, sind für Kernel- und User-Space-Programme zugänglich, die auf der nicht sicheren Seite ausgeführt werden.

Die Ausführungsumgebung auf der nicht sicheren Seite (Kernel und Benutzerbereich) unterscheidet sich drastisch von der Ausführungsumgebung auf der sicheren Seite. Daher gibt es statt einer einzigen Bibliothek für beide Umgebungen zwei verschiedene Sätze von APIs. Im Kernel wird die Client-API vom Kerneltreiber „trusty-ipc“ bereitgestellt und registriert einen Zeichengeräteknoten, der von User-Space-Prozessen zur Kommunikation mit Diensten verwendet werden kann, die auf der sicheren Seite ausgeführt werden.

Benutzerbereich Trusty IPC Client API

Die User Space Trusty IPC Client API-Bibliothek ist eine dünne Schicht über dem Geräteknoten fd .

Ein User-Space-Programm startet eine Kommunikationssitzung durch den Aufruf tipc_connect() und initialisiert so eine Verbindung zu einem angegebenen Trusty-Dienst. Intern öffnet der Aufruf tipc_connect() einen angegebenen Geräteknoten, um einen Dateideskriptor abzurufen, und ruft einen Aufruf TIPC_IOC_CONNECT ioctl() auf, wobei der Parameter argp auf eine Zeichenfolge zeigt, die einen Dienstnamen enthält, zu dem eine Verbindung hergestellt werden soll.

#define TIPC_IOC_MAGIC  'r'
#define TIPC_IOC_CONNECT  _IOW(TIPC_IOC_MAGIC, 0x80, char *)

Der resultierende Dateideskriptor kann nur zur Kommunikation mit dem Dienst verwendet werden, für den er erstellt wurde. Der Dateideskriptor sollte durch Aufruf tipc_close() geschlossen werden, wenn die Verbindung nicht mehr benötigt wird.

Der durch den Aufruf von tipc_connect() erhaltene Dateideskriptor verhält sich wie ein typischer Zeichengeräteknoten; der Dateideskriptor:

  • Kann bei Bedarf in den nicht blockierenden Modus geschaltet werden
  • Kann mithilfe eines standardmäßigen write() -Aufrufs beschrieben werden, um Nachrichten an die andere Seite zu senden
  • Kann (mit poll() Aufrufen oder select() Aufrufen) auf Verfügbarkeit eingehender Nachrichten als regulärer Dateideskriptor abgefragt werden
  • Kann gelesen werden, um eingehende Nachrichten abzurufen

Ein Anrufer sendet eine Nachricht an den Trusty-Dienst, indem er einen Schreibaufruf für den angegebenen fd ausführt. Alle an den obigen write() -Aufruf übergebenen Daten werden vom Trusty-IPC-Treiber in eine Nachricht umgewandelt. Die Nachricht wird an die sichere Seite übermittelt, wo die Daten vom IPC-Subsystem im Trusty-Kernel verarbeitet, an das richtige Ziel weitergeleitet und als IPC_HANDLE_POLL_MSG Ereignis auf einem bestimmten Kanalhandle an eine App-Ereignisschleife übermittelt werden. Abhängig vom jeweiligen dienstspezifischen Protokoll kann der Trusty-Dienst eine oder mehrere Antwortnachrichten senden, die an die nicht sichere Seite zurückgesendet und in die Nachrichtenwarteschlange des entsprechenden Kanaldateideskriptors gestellt werden, um von der User-Space-Anwendung read() Anruf.

tipc_connect()

Öffnet einen angegebenen tipc Geräteknoten und initiiert eine Verbindung zu einem angegebenen Trusty-Dienst.

int tipc_connect(const char *dev_name, const char *srv_name);

[in] dev_name : Pfad zum zu öffnenden Trusty IPC-Geräteknoten

[in] srv_name : Name eines veröffentlichten Trusty-Dienstes, zu dem eine Verbindung hergestellt werden soll

[retval]: Gültiger Dateideskriptor bei Erfolg, andernfalls -1.

tipc_close()

Schließt die Verbindung zum durch einen Dateideskriptor angegebenen Trusty-Dienst.

int tipc_close(int fd);

[in] fd : Dateideskriptor, der zuvor durch einen tipc_connect() -Aufruf geöffnet wurde

Kernel Trusty IPC Client API

Für Kernel-Treiber ist die Kernel Trusty IPC Client API verfügbar. Die User Space Trusty IPC API wird auf dieser API implementiert.

Im Allgemeinen besteht die typische Verwendung dieser API darin, dass ein Aufrufer ein struct tipc_chan Objekt erstellt, indem er die Funktion tipc_create_channel() verwendet und dann den Aufruf tipc_chan_connect() verwendet, um eine Verbindung zum Trusty IPC-Dienst zu initiieren, der auf der sicheren Seite ausgeführt wird. Die Verbindung zur Remote-Seite kann durch den Aufruf tipc_chan_shutdown() gefolgt von tipc_chan_destroy() beendet werden, um Ressourcen zu bereinigen.

Nach Erhalt einer Benachrichtigung (über den handle_event() -Rückruf), dass eine Verbindung erfolgreich hergestellt wurde, führt ein Anrufer Folgendes aus:

  • Ruft einen Nachrichtenpuffer mit dem Aufruf tipc_chan_get_txbuf_timeout() ab
  • Verfasst eine Nachricht und
  • Stellt die Nachricht mithilfe der Methode tipc_chan_queue_msg() in die Warteschlange für die Übermittlung an einen vertrauenswürdigen Dienst (auf der sicheren Seite), mit dem der Kanal verbunden ist

Nachdem die Warteschlange erfolgreich war, sollte der Aufrufer den Nachrichtenpuffer vergessen, da der Nachrichtenpuffer schließlich nach der Verarbeitung durch die Remote-Seite in den freien Pufferpool zurückkehrt (zur späteren Wiederverwendung für andere Nachrichten). Der Benutzer muss nur dann tipc_chan_put_txbuf() aufrufen, wenn die Warteschlange für diesen Puffer fehlschlägt oder er nicht mehr benötigt wird.

Ein API-Benutzer empfängt Nachrichten von der Remote-Seite, indem er einen handle_msg() Benachrichtigungsrückruf (der im Kontext der rx Arbeitswarteschlange von „trusty-ipc“ aufgerufen wird) verarbeitet, der einen Zeiger auf einen rx Puffer bereitstellt, der eine zu verarbeitende eingehende Nachricht enthält.

Es wird erwartet, dass die handle_msg() -Callback-Implementierung einen Zeiger auf eine gültige struct tipc_msg_buf zurückgibt. Es kann mit dem Puffer für eingehende Nachrichten identisch sein, wenn er lokal verwaltet wird und nicht mehr benötigt wird. Alternativ kann es sich um einen neuen Puffer handeln, der durch einen Aufruf von tipc_chan_get_rxbuf() erhalten wird, wenn der eingehende Puffer zur weiteren Verarbeitung in die Warteschlange gestellt wird. Ein getrennter rx Puffer muss nachverfolgt und schließlich mit einem tipc_chan_put_rxbuf() Aufruf freigegeben werden, wenn er nicht mehr benötigt wird.

Methoden in der Kernel Trusty IPC Client API

tipc_create_channel()

Erstellt und konfiguriert eine Instanz eines Trusty IPC-Kanals für ein bestimmtes Trusty-IPC-Gerät.

struct tipc_chan *tipc_create_channel(struct device *dev,
                          const struct tipc_chan_ops *ops,
                              void *cb_arg);

[in] dev : Zeiger auf den Trusty-IPC, für den der Gerätekanal erstellt wird

[in] ops : Zeiger auf eine struct tipc_chan_ops , mit ausgefüllten anruferspezifischen Rückrufen

[in] cb_arg : Zeiger auf Daten, die an tipc_chan_ops Rückrufe übergeben werden

[retval]: Zeiger auf eine neu erstellte Instanz der struct tipc_chan bei Erfolg, andernfalls ERR_PTR(err)

Im Allgemeinen muss ein Aufrufer zwei Rückrufe bereitstellen, die asynchron aufgerufen werden, wenn die entsprechende Aktivität stattfindet.

Das void (*handle_event)(void *cb_arg, int event) wird aufgerufen, um einen Anrufer über eine Kanalstatusänderung zu benachrichtigen.

[in] cb_arg : Zeiger auf Daten, die an einen tipc_create_channel() Aufruf übergeben werden

[in] event : Ein Ereignis, das einen der folgenden Werte annehmen kann:

  • TIPC_CHANNEL_CONNECTED – zeigt eine erfolgreiche Verbindung zur Remote-Seite an
  • TIPC_CHANNEL_DISCONNECTED – zeigt an, dass die Gegenseite die neue Verbindungsanforderung abgelehnt oder die Trennung für den zuvor verbundenen Kanal angefordert hat
  • TIPC_CHANNEL_SHUTDOWN – zeigt an, dass die Remote-Seite herunterfährt und alle Verbindungen dauerhaft beendet werden

Der Rückruf struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) wird aufgerufen, um eine Benachrichtigung bereitzustellen, dass eine neue Nachricht über einen angegebenen Kanal empfangen wurde:

  • [in] cb_arg : Zeiger auf Daten, die an den Aufruf tipc_create_channel() übergeben werden
  • [in] mb : Zeiger auf eine struct tipc_msg_buf , die eine eingehende Nachricht beschreibt
  • [retval]: Von der Callback-Implementierung wird erwartet, dass sie einen Zeiger auf eine struct tipc_msg_buf zurückgibt, bei der es sich um denselben Zeiger handeln kann, der als mb Parameter empfangen wurde, wenn die Nachricht lokal verarbeitet wird und nicht mehr benötigt wird (oder es kann ein neuer Puffer sein, der von der erhalten wurde). tipc_chan_get_rxbuf() Aufruf)

tipc_chan_connect()

Initiiert eine Verbindung zum angegebenen Trusty IPC-Dienst.

int tipc_chan_connect(struct tipc_chan *chan, const char *port);

[in] chan : Zeiger auf einen Kanal, der vom Aufruf tipc_create_chan() zurückgegeben wird

[in] port : Zeiger auf eine Zeichenfolge, die den Dienstnamen enthält, zu dem eine Verbindung hergestellt werden soll

[retval]: 0 bei Erfolg, andernfalls ein negativer Fehler

Der Anrufer wird benachrichtigt, wenn eine Verbindung hergestellt wird, indem er einen handle_event Rückruf erhält.

tipc_chan_shutdown()

Beendet eine Verbindung zum Trusty IPC-Dienst, die zuvor durch einen Aufruf tipc_chan_connect() initiiert wurde.

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : Zeiger auf einen Kanal, der von einem tipc_create_chan() Aufruf zurückgegeben wird

tipc_chan_destroy()

Zerstört einen angegebenen Trusty IPC-Kanal.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : Zeiger auf einen Kanal, der vom Aufruf tipc_create_chan() zurückgegeben wird

tipc_chan_get_txbuf_timeout()

Ruft einen Nachrichtenpuffer ab, der zum Senden von Daten über einen angegebenen Kanal verwendet werden kann. Wenn der Puffer nicht sofort verfügbar ist, wird der Aufrufer möglicherweise für die angegebene Zeitüberschreitung (in Millisekunden) blockiert.

struct tipc_msg_buf *
tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);

[in] chan : Zeiger auf den Kanal, in den eine Nachricht in die Warteschlange gestellt werden soll

[in] chan : Maximale Wartezeit, bis der tx verfügbar wird

[retval]: Ein gültiger Nachrichtenpuffer bei Erfolg, ERR_PTR(err) bei Fehler

tipc_chan_queue_msg()

Stellt eine Nachricht in die Warteschlange, die über die angegebenen Trusty IPC-Kanäle gesendet werden soll.

int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);

[in] chan : Zeiger auf den Kanal, in den die Nachricht eingereiht werden soll

[in] mb: Zeiger auf die in die Warteschlange einzureihende Nachricht (erhalten durch einen Aufruf tipc_chan_get_txbuf_timeout() )

[retval]: 0 bei Erfolg, andernfalls ein negativer Fehler

tipc_chan_put_txbuf()

Gibt den angegebenen Tx Nachrichtenpuffer frei, der zuvor durch einen Aufruf tipc_chan_get_txbuf_timeout() abgerufen wurde.

void tipc_chan_put_txbuf(struct tipc_chan *chan,
                         struct tipc_msg_buf *mb);

[in] chan : Zeiger auf den Kanal, zu dem dieser Nachrichtenpuffer gehört

[in] mb : Zeiger auf den freizugebenden Nachrichtenpuffer

[retval]: Keine

tipc_chan_get_rxbuf()

Ruft einen neuen Nachrichtenpuffer ab, der zum Empfangen von Nachrichten über den angegebenen Kanal verwendet werden kann.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : Zeiger auf einen Kanal, zu dem dieser Nachrichtenpuffer gehört

[retval]: Ein gültiger Nachrichtenpuffer bei Erfolg, ERR_PTR(err) bei Fehler

tipc_chan_put_rxbuf()

Gibt einen angegebenen Nachrichtenpuffer frei, der zuvor durch einen Aufruf tipc_chan_get_rxbuf() abgerufen wurde.

void tipc_chan_put_rxbuf(struct tipc_chan *chan,
                         struct tipc_msg_buf *mb);

[in] chan : Zeiger auf einen Kanal, zu dem dieser Nachrichtenpuffer gehört

[in] mb : Zeiger auf einen freizugebenden Nachrichtenpuffer

[retval]: Keine