Trusty API-Referenz

Trusty bietet APIs für die Entwicklung von zwei Arten von Apps und Diensten:

  • Vertrauenswürdige Apps und Dienste, die auf dem TEE-Prozessor ausgeführt werden
  • Normale und nicht vertrauenswürdige Apps, die auf dem Hauptprozessor ausgeführt werden und die von vertrauenswürdigen Apps bereitgestellten Dienste verwenden

Die Trusty API beschreibt im Allgemeinen das Trusty-IPC-System (Inter-Process Communication), einschließlich der Kommunikation mit der nicht sicheren Welt. Software, die auf dem Hauptprozessor ausgeführt wird, kann Trusty APIs verwenden, um eine Verbindung zu vertrauenswürdigen Apps und Diensten herzustellen und beliebige Nachrichten mit ihnen auszutauschen, genau wie ein Netzwerkdienst über IP. Es liegt an der App, 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. Die Kommunikation ist vollständig asynchron.

Ports und Kanäle

Ports werden von Trusty-Apps verwendet, um Dienstendpunkte in Form eines benannten Pfads bereitzustellen, zu dem Clients eine Verbindung herstellen. So erhalten Clients eine einfache, stringsbasierte Dienst-ID. Die Namenskonvention entspricht dem Reverse-DNS-Namensstil, z.B. com.google.servicename.

Wenn ein Client eine Verbindung zu einem Port herstellt, erhält er einen Kanal für die Interaktion mit einem Dienst. Der Dienst muss eine eingehende Verbindung akzeptieren. In diesem Fall erhält er ebenfalls einen Kanal. Im Grunde werden Ports verwendet, um Dienste abzurufen. Die Kommunikation erfolgt dann über ein Paar verbundener Kanäle (d. h. Verbindungsinstanzen an einem Port). Wenn ein Client eine Verbindung zu einem Port herstellt, wird eine symmetrische, bidirektionale Verbindung hergestellt. Über diesen Full-Duplex-Pfad können Clients und Server beliebige Nachrichten austauschen, bis eine der beiden Seiten die Verbindung auflöst.

Nur vertrauenswürdige Anwendungen auf der sicheren Seite oder Trusty-Kernelmodule können Ports erstellen. Apps, 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 wurden.

Je nach Anforderungen kann eine vertrauenswürdige App sowohl ein Client als auch ein Server sein. Eine vertrauenswürdige App, die einen Dienst veröffentlicht (als Server), muss möglicherweise eine Verbindung zu anderen Diensten herstellen (als Client).

Handle API

Handles sind nicht signierte Ganzzahlen, die Ressourcen wie Ports und Kanäle repräsentieren, ähnlich wie Dateideskriptoren unter UNIX. Nach dem Erstellen werden die Handles in eine appspezifische Handle-Tabelle eingefügt und können später referenziert werden.

Ein Aufrufer kann mithilfe der Methode set_cookie() private Daten einem Handle zuordnen.

Methoden in der Handle API

Handles sind nur im Kontext einer App gültig. Eine App darf den Wert eines Handles nur dann an andere Apps weitergeben, wenn dies ausdrücklich angegeben ist. Ein Handle-Wert sollte nur durch Vergleich mit dem INVALID_IPC_HANDLE #define, interpretiert werden, den eine App als Hinweis darauf verwenden kann, dass ein Handle ungültig oder nicht festgelegt ist.

Verknüpft die vom Anrufer bereitgestellten privaten Daten mit einem bestimmten Handle.

long set_cookie(uint32_t handle, void *cookie)

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

[in] cookie: Pointer auf beliebige User-Space-Daten in der Trusty App

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

Dieser Aufruf ist nützlich, um Ereignisse zu verarbeiten, die nach dem Erstellen des Handlers auftreten. Der Ereignis-Handling-Mechanismus gibt den Handle und sein Cookie an den Ereignis-Handler zurück.

Mit dem Aufruf wait() können Sie auf Ereignisse warten, die mit einem Handle verknüpft sind.

wait()

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

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

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

[out] event: Ein Verweis auf die Struktur, die ein Ereignis darstellt, das für diesen Handle aufgetreten ist

[in] timeout_msecs: Ein Zeitlimit in Millisekunden. Der Wert „-1“ entspricht einem unendlichen Zeitlimit.

[retval]: NO_ERROR, wenn innerhalb eines bestimmten Zeitlimits ein gültiges Ereignis aufgetreten ist; ERR_TIMED_OUT, wenn ein bestimmter Zeitlimit abgelaufen ist, aber kein Ereignis aufgetreten ist; < 0 bei anderen Fehlern

Bei Erfolg (retval == NO_ERROR) füllt der wait()-Aufruf eine angegebene uevent_t-Struktur mit Informationen zum aufgetretenen 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 Feld 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 sind keine Ereignisse ausstehend. Der Aufrufer sollte die Wartezeit neu starten.

IPC_HANDLE_POLL_ERROR – ein nicht spezifizierter interner Fehler ist aufgetreten

IPC_HANDLE_POLL_READY – hängt vom Typ des Alias ab:

  • Bei Ports gibt dieser Wert an, dass eine ausstehende Verbindung besteht.
  • 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 – gibt an, dass ein Kanal von einem Peer geschlossen wurde
  • IPC_HANDLE_POLL_MSG: Gibt an, dass für diesen Kanal eine ausstehende Nachricht vorhanden ist.
  • IPC_HANDLE_POLL_SEND_UNBLOCKED: Ein zuvor blockierter Anrufer versucht möglicherweise noch einmal, eine Nachricht zu senden. Weitere Informationen finden Sie in der Beschreibung von send_msg().

Ein Ereignis-Handler sollte für die Verarbeitung einer Kombination von Ereignissen bereit sein, da mehrere Bits gleichzeitig gesetzt werden können. Beispielsweise kann es bei einem Channel vorkommen, dass ausstehende Nachrichten vorliegen und gleichzeitig eine Verbindung von einem Peer geschlossen wird.

Die meisten Ereignisse sind fixiert. Sie bleiben so lange bestehen, wie die zugrunde liegende Bedingung erfüllt ist (z. B. bis alle ausstehenden Nachrichten empfangen und alle ausstehenden Verbindungsanfragen verarbeitet wurden). Eine Ausnahme ist das IPC_HANDLE_POLL_SEND_UNBLOCKED-Ereignis, das nach dem Lesen gelöscht wird und bei dem die App nur eine Chance hat, es zu verarbeiten.

Sie können Handles durch Aufrufen der Methode close() löschen.

close()

Löscht die mit dem angegebenen Handle verknüpfte Ressource und entfernt sie aus der Handletabelle.

long close(uint32_t handle_id);

[in] handle_id: Handle zum Löschen

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

Server API

Ein Server erstellt zuerst einen oder mehrere benannte Ports, die seine Dienstendpunkte darstellen. Jeder Anschluss wird durch einen Handle dargestellt.

Methoden in der Server API

port_create()

Erstellt einen benannten Dienstport.

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

[in] path: Der Stringname des Anschlusses (wie oben beschrieben). Dieser Name muss im gesamten System eindeutig sein. Versuche, ein Duplikat zu erstellen, schlagen fehl.

[in] num_recv_bufs: Die maximale Anzahl von Puffern, die ein Kanal an diesem Port vorab zuweisen kann, um den Austausch von Daten mit dem Client zu erleichtern. Puffer werden für Daten in beide Richtungen separat gezählt. Wenn Sie hier „1“ angeben, werden also ein Sende- und ein Empfangspuffer vorab zugewiesen. Im Allgemeinen hängt die Anzahl der erforderlichen Puffer von der Protokollvereinbarung der höheren Ebene zwischen Client und Server ab. Bei einem sehr synchronen Protokoll kann die Anzahl sogar nur 1 betragen (Nachricht senden, Antwort erhalten, bevor eine weitere gesendet wird). Die Anzahl kann jedoch höher sein, wenn der Client davon ausgeht, dass mehr als eine Nachricht gesendet werden muss, bevor eine Antwort angezeigt werden kann (z. B. eine Nachricht als Prolog und eine andere als tatsächlicher Befehl). Die zugewiesenen Puffersätze sind pro Kanal. Zwei separate Verbindungen (Kanäle) haben also separate Puffersätze.

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

[in] flags: Eine Kombination von Flags, die das zusätzliche Verhalten des Ports angibt

Dieser Wert sollte eine Kombination der folgenden Werte sein:

IPC_PORT_ALLOW_TA_CONNECT – erlaubt eine Verbindung von anderen sicheren Apps

IPC_PORT_ALLOW_NS_CONNECT – ermöglicht eine Verbindung aus dem nicht sicheren Internet

[retval]: Handle des erstellten Ports, falls nicht negativ, oder ein bestimmter Fehler, falls negativ

Der Server prüft dann mit dem wait()-Aufruf die Liste der Port-Handles auf eingehende Verbindungen. Wenn der Server eine Verbindungsanfrage erhält, die durch das im Feld event der Struktur uevent_t festgelegte IPC_HANDLE_POLL_READY-Bit gekennzeichnet ist, sollte er accept() aufrufen, um die Verbindung herzustellen und einen Kanal (durch einen anderen Handle dargestellt) zu erstellen, der dann auf eingehende Nachrichten gepollt werden kann.

accept()

Akzeptiert eine eingehende Verbindung und ruft einen Handle für einen Kanal ab.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

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

[out] peer_uuid: Pointer auf eine uuid_t-Struktur, die mit der UUID der verbindenden Client-App ausgefüllt wird. Wenn die Verbindung aus der nicht sicheren Welt stammt, wird der Wert auf Null gesetzt.

[retval]: Handle zu einem Kanal (falls nicht negativ), über den der Server Nachrichten mit dem Client austauschen kann (andernfalls ein Fehlercode)

Client API

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

Methoden in der Client API

connect()

Hiermit wird eine Verbindung zu einem Port hergestellt, der per Name angegeben wird.

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

[in] path: Name eines Ports, der von einer vertrauenswürdigen App veröffentlicht wurde

[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 bei negativem Wert

Wenn keine flags angegeben sind (der Parameter flags ist auf 0 gesetzt), wird durch Aufrufen von connect() eine synchrone Verbindung zu einem angegebenen Port hergestellt, die sofort einen Fehler zurückgibt, wenn der Port nicht existiert. Außerdem wird eine Blockierung erstellt, bis der Server anderweitig eine Verbindung akzeptiert.

Dieses Verhalten kann geändert werden, indem Sie eine Kombination aus zwei Werten angeben, die unten beschrieben werden:

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 Anschluss bei der Ausführung nicht sofort vorhanden ist, anstatt sofort fehlzuschlagen.

IPC_CONNECT_ASYNC: Wenn festgelegt, wird eine asynchrone Verbindung initiiert. Eine App muss den zurückgegebenen Handle abfragen, indem sie wait() für ein Verbindungsabschlussereignis aufruft, das durch das im Ereignisfeld der Struktur uevent_t festgelegte IPC_HANDLE_POLL_READY-Bit angezeigt wird, bevor der normale Betrieb gestartet wird.

Messaging API

Mit den Messaging API-Aufrufen können Nachrichten über eine zuvor hergestellte Verbindung (Kanal) gesendet und gelesen werden. Die Messaging API-Aufrufe sind für Server und Clients identisch.

Ein Client erhält einen Kanal-Handle, indem er einen connect()-Aufruf ausführt. Ein Server erhält einen Kanal-Handle über einen accept()-Aufruf, wie oben beschrieben.

Struktur einer Trusty-Mitteilung

Wie unten dargestellt, haben Nachrichten, die über die Trusty API ausgetauscht werden, eine minimale Struktur. Server und Client müssen sich also auf die Semantik des tatsächlichen Inhalts 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 Lese- und Schreibvorgänge für diese Blöcke aus. Der Inhalt von Puffern, die durch das iov-Array beschrieben werden können, ist völlig beliebig.

Methoden in der Messaging API

send_msg()

Sendet eine Nachricht über einen bestimmten Kanal.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle: Handle des Kanals, über den die Nachricht gesendet werden soll

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

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

Wenn der Client (oder Server) versucht, eine Nachricht über den Kanal zu senden und in der Nachrichtenwarteschlange des Ziel-Peers kein Speicherplatz vorhanden ist, wechselt der Kanal möglicherweise in den Sendeblockierungsstatus. Dies sollte bei einem einfachen synchronen Anfrage/Antwort-Protokoll nie passieren, kann aber in komplizierteren Fällen vorkommen. Dies wird durch die Rückgabe eines ERR_NOT_ENOUGH_BUFFER-Fehlercodes angezeigt. In diesem Fall muss der Aufrufer warten, bis der Peer Speicherplatz in seiner Empfangswarteschlange freigibt, indem er die zu verarbeitenden und zu entfernenden Nachrichten abholt. Dies wird durch das Setzen des IPC_HANDLE_POLL_SEND_UNBLOCKED-Bits im event-Feld der uevent_t-Struktur angezeigt, die vom wait()-Aufruf zurückgegeben wird.

get_msg()

Ruft Metainformationen zur nächsten 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: Alias des Kanals, für den eine neue Nachricht abgerufen werden soll

[out] msg_info: Die Struktur der Nachrichteninformationen wird folgendermaßen beschrieben:

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

Jede Nachricht erhält eine eindeutige ID und die Gesamtlänge jeder Nachricht wird ausgefüllt. Wenn dies vom Protokoll konfiguriert und zulässig ist, 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: Alias des Kanals, aus dem die Nachricht gelesen werden soll

[in] msg_id: ID der Nachricht, die gelesen werden soll

[in] offset: Offset in der Nachricht, ab dem gelesen werden soll

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

[retval]: Die Gesamtzahl der Byte, die bei Erfolg in den msg-Buffern gespeichert werden. Andernfalls ein negativer Fehler.

Die read_msg-Methode kann bei Bedarf mehrmals aufgerufen werden, wobei der Offset jeweils unterschiedlich (nicht unbedingt sequenziell) sein kann.

put_msg()

Eine Nachricht mit einer bestimmten ID wird inaktiviert.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle: Alias des Kanals, auf dem die Nachricht eingegangen ist

[in] msg_id: ID der Nachricht, die eingestellt wird

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

Nach dem Entfernen einer Nachricht und dem Freigeben des von ihr belegten Buffers kann nicht mehr auf den Inhalt der Nachricht zugegriffen werden.

File Descriptor API

Die File Descriptor API umfasst die Aufrufe read(), write() und ioctl(). Alle diese Aufrufe können auf einem vordefinierten (statischen) Satz von Dateienbeschreibern ausgeführt werden, die traditionell durch kleine Zahlen dargestellt werden. In der aktuellen Implementierung ist der Dateideskriptorbereich vom IPC-Handlebereich getrennt. Die File Descriptor API in Trusty ähnelt einer herkömmlichen dateideskriptorbasierten API.

Standardmäßig gibt es drei vordefinierte (standardmäßige und bekannte) Dateideskriptoren:

  • 0 – Standardeingabe. Die Standardimplementierung der Standardeingabe fd ist ein No-Op, da vertrauenswürdige Apps keine interaktive Konsole haben sollten. Wenn Sie ioctl() auf fd 0 lesen, schreiben oder aufrufen, sollte ein ERR_NOT_SUPPORTED-Fehler zurückgegeben werden.
  • 1 – Standardausgabe Daten, die in die Standardausgabe geschrieben werden, können je nach Plattform und Konfiguration (je nach LK-Debugebene) an UART und/oder ein Speicherprotokoll auf der nicht sicheren Seite weitergeleitet werden. Nicht kritische Debug-Logs und Meldungen sollten an die Standardausgabe geleitet werden. Die Methoden read() und ioctl() sind No-Ops und sollten einen ERR_NOT_SUPPORTED-Fehler zurückgeben.
  • 2 – Standardfehler. Daten, die in den Standardfehler geschrieben werden, sollten je nach Plattform und Konfiguration an das UART- oder Speicherprotokoll weitergeleitet werden, das sich auf der nicht sicheren Seite befindet. Es wird empfohlen, nur kritische Nachrichten in den Standardfehler zu schreiben, da dieser Stream sehr wahrscheinlich nicht gedrosselt wird. Die Methoden read() und ioctl() sind No-Ops und sollten einen ERR_NOT_SUPPORTED-Fehler zurückgeben.

Dieser Satz von Dateideskriptoren kann zwar erweitert werden, um mehr fds (plattformspezifische Erweiterungen) zu implementieren, die Erweiterung von Dateideskriptoren sollte jedoch mit Vorsicht erfolgen. Das Erweitern von Dateideskriptoren führt häufig zu Konflikten und wird im Allgemeinen nicht empfohlen.

Methoden in der File Descriptor API

read()

Es wird versucht, bis zu count Byte Daten aus einem bestimmten Dateideskriptor 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 Byte

[retval]: Anzahl der gelesenen Byte; andernfalls ein negativer Fehler

write()

Schreibt bis zu count Byte 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: Pointer auf zu schreibende Daten

[in] count: Maximale Anzahl von Byte, die geschrieben werden sollen

[retval]: Die Anzahl der geschriebenen Bytes. Andernfalls ein negativer Fehler.

ioctl()

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

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

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

[in] cmd: Der Befehl ioctl

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

Sonstige API

Methoden in der Miscellaneous API

gettime()

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; „0“ für Standard übergeben

[in] flags: Reserviert, sollte null sein

[out] time: Verweis auf einen int64_t-Wert, in dem die aktuelle Uhrzeit gespeichert werden soll

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

nanosleep()

Die Ausführung der aufrufenden App wird für einen bestimmten Zeitraum pausiert und nach Ablauf dieses Zeitraums fortgesetzt.

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 für einen vertrauenswürdigen App-Server

In der folgenden Beispielanwendung wird die Verwendung der oben genannten APIs veranschaulicht. Im Beispiel wird ein „Echo“-Dienst erstellt, der mehrere eingehende Verbindungen verarbeitet und alle Nachrichten, die er von Clients empfängt, an den Anrufer zurücksendet, unabhängig davon, ob sie von der sicheren oder nicht sicheren Seite stammen.

#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 app 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 run_end_to_end_msg_test()-Methode sendet 10.000 Nachrichten asynchron an den „echo“-Dienst und verarbeitet die 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;
}

APIs und Apps in nicht sicheren Umgebungen

Eine Reihe von Trusty-Diensten, die von der sicheren Seite aus veröffentlicht und mit dem Attribut IPC_PORT_ALLOW_NS_CONNECT gekennzeichnet sind, sind für Kernel- und Userspace-Programme zugänglich, die auf der nicht sicheren Seite ausgeführt werden.

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

User Space Trusty IPC Client API

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

Ein User-Space-Programm startet eine Kommunikationssitzung, indem es tipc_connect() aufruft und eine Verbindung zu einem bestimmten Trusty-Dienst initialisiert. Intern öffnet der tipc_connect()-Aufruf einen bestimmten Geräteknoten, um einen Dateideskriptor abzurufen, und ruft einen TIPC_IOC_CONNECT ioctl()-Aufruf mit dem argp-Parameter auf, der auf einen String mit einem Dienstnamen verweist, 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 für die Kommunikation mit dem Dienst verwendet werden, für den er erstellt wurde. Der Dateideskriptor sollte geschlossen werden, indem tipc_close() aufgerufen wird, wenn die Verbindung nicht mehr benötigt wird.

Der Dateideskriptor, der durch den tipc_connect()-Aufruf zurückgegeben wird, verhält sich wie ein typischer Knoten eines Zeichengeräts. Der Dateideskriptor:

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

Ein Aufrufer sendet eine Nachricht an den Trusty-Dienst, indem er einen Schreibaufruf für die angegebene fd ausführt. Alle Daten, die an den obigen write()-Aufruf übergeben werden, werden vom trusty-ipc-Treiber in eine Nachricht umgewandelt. Die Nachricht wird an die sichere Seite gesendet, wo die Daten vom IPC-Subsystem im Trusty-Kernel verarbeitet und an das richtige Ziel weitergeleitet werden. Sie wird dann als IPC_HANDLE_POLL_MSG-Ereignis über einen bestimmten Channel-Handle an einen App-Ereignis-Loop gesendet. Je nach dem jeweiligen dienstspezifischen Protokoll sendet der Trusty-Dienst eine oder mehrere Antwortnachrichten, die an die nicht sichere Seite zurückgesendet und in die entsprechende Nachrichtenwarteschlange für den Dateideskriptor des Kanals gestellt werden, um vom read()-Aufruf der Nutzerbereichs-App abgerufen zu werden.

tipc_connect()

Öffnet einen bestimmten tipc-Geräteknoten und initiiert eine Verbindung zu einem bestimmten 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 Trusty-Dienst, die durch einen Dateideskriptor angegeben wurde.

int tipc_close(int fd);

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

Kernel Trusty IPC Client API

Die Kernel Trusty IPC Client API ist für Kerneltreiber verfügbar. Die Trusty IPC API für den Nutzerbereich wird auf dieser API implementiert.

Bei der typischen Verwendung dieser API erstellt ein Aufrufer ein struct tipc_chan-Objekt mithilfe der Funktion tipc_create_channel() und initiiert dann mit dem Aufruf tipc_chan_connect() eine Verbindung zum Trusty IPC-Dienst, der auf der sicheren Seite ausgeführt wird. Die Verbindung zur Remote-Seite kann durch Eingabe von tipc_chan_shutdown() und dann tipc_chan_destroy() beendet werden, um Ressourcen zu bereinigen.

Wenn ein Anrufer über den handle_event()-Callback benachrichtigt wird, dass eine Verbindung hergestellt wurde, geschieht Folgendes:

  • Ruft mit dem tipc_chan_get_txbuf_timeout()-Aufruf einen Nachrichtenpuffer ab
  • Verfasst eine Nachricht und
  • Die Nachricht wird mit der tipc_chan_queue_msg()-Methode zur Zustellung an einen Trusty-Dienst (auf der sicheren Seite) in die Warteschlange gestellt, mit dem der Channel verbunden ist.

Nach der erfolgreichen Einreihung in die Warteschlange sollte der Aufrufer den Nachrichtenbuffer vergessen, da er nach der Verarbeitung durch die Remote-Seite wieder in den kostenlosen Bufferpool zurückgegeben wird, um später für andere Nachrichten wiederverwendet zu werden. Der Nutzer muss tipc_chan_put_txbuf() nur aufrufen, wenn ein solcher Puffer nicht in die Warteschlange gestellt werden kann oder nicht mehr benötigt wird.

Ein API-Nutzer empfängt Nachrichten von der Remote-Seite, indem er einen handle_msg()-Benachrichtigungs-Callback verarbeitet, der im Kontext des rx-Arbeitsqueues von trusty-ipc aufgerufen wird und einen Verweis auf einen rx-Puffer mit einer zu verarbeitenden eingehenden Nachricht enthält.

Die handle_msg()-Callback-Implementierung sollte einen Verweis auf eine gültige struct tipc_msg_buf zurückgeben. Er kann mit dem Puffer für eingehende Nachrichten identisch sein, wenn er lokal verarbeitet wird und nicht mehr benötigt wird. Alternativ kann es sich um einen neuen Puffer handeln, der durch einen tipc_chan_get_rxbuf()-Aufruf abgerufen wird, wenn der eingehende Puffer für die weitere Verarbeitung in die Warteschlange gestellt wird. Ein getrennter rx-Puffer muss überwacht 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: Verweis auf das Trusty-IPC, für das der Gerätekanal erstellt wird

[in] ops: Verweis auf eine struct tipc_chan_ops, mit ausgefüllten rufendenspezifischen Callbacks

[in] cb_arg: Verweis auf Daten, die an tipc_chan_ops-Callbacks übergeben werden

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

Im Allgemeinen muss ein Aufrufer zwei Callbacks bereitstellen, die asynchron aufgerufen werden, wenn die entsprechende Aktivität auftritt.

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

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

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

  • TIPC_CHANNEL_CONNECTED – gibt eine erfolgreiche Verbindung zur Remote-Seite an
  • TIPC_CHANNEL_DISCONNECTED: Gibt an, dass die Remote-Seite die neue Verbindungsanfrage abgelehnt oder die Verbindung für den zuvor verbundenen Kanal getrennt hat.
  • TIPC_CHANNEL_SHUTDOWN – gibt an, dass die Remote-Seite heruntergefahren wird und alle Verbindungen dauerhaft beendet werden

Der struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)-Callback wird aufgerufen, um zu benachrichtigen, dass eine neue Nachricht über einen bestimmten Kanal empfangen wurde:

  • [in] cb_arg: Verweis auf Daten, die an den tipc_create_channel()-Aufruf übergeben werden
  • [in] mb: Verweis auf eine struct tipc_msg_buf, die eine eingehende Nachricht beschreibt
  • [retval]: Die Callback-Implementierung sollte einen Verweis auf eine struct tipc_msg_buf zurückgeben. Dies kann derselbe Verweis sein, der als mb-Parameter empfangen wurde, wenn die Nachricht lokal verarbeitet wird und nicht mehr benötigt wird. Es kann sich auch um einen neuen Puffer handeln, der durch den tipc_chan_get_rxbuf()-Aufruf abgerufen wurde.

tipc_chan_connect()

Hiermit wird eine Verbindung zum angegebenen Trusty-IPC-Dienst hergestellt.

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

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

[in] port: Verweis auf einen String mit dem Namen des Dienstes, zu dem eine Verbindung hergestellt werden soll

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

Der Anrufer wird über einen handle_event-Callback benachrichtigt, wenn eine Verbindung hergestellt wurde.

tipc_chan_shutdown()

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

int tipc_chan_shutdown(struct tipc_chan *chan);

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

tipc_chan_destroy()

Löscht einen angegebenen Trusty-IPC-Kanal.

void tipc_chan_destroy(struct tipc_chan *chan);

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

tipc_chan_get_txbuf_timeout()

Ruft einen Nachrichtenpuffer ab, mit dem Daten über einen bestimmten Kanal gesendet werden können. Wenn der Puffer nicht sofort verfügbar ist, wird der Anrufer 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: Verweis auf den Kanal, in dem eine Nachricht in die Warteschlange gestellt werden soll

[in] chan: Maximale Zeitüberschreitung, bis der tx-Puffer verfügbar ist

[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: Verweis auf den Kanal, in der die Nachricht eingereiht werden soll

[in] mb: Verweise auf die Nachricht, die in die Warteschlange gestellt werden soll (über einen tipc_chan_get_txbuf_timeout()-Aufruf abgerufen)

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

tipc_chan_put_txbuf()

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

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

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

[in] mb: Zeiger auf den zu freizugebenden Nachrichtenbuffer

[retval]: None

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: Verweis auf einen Kanal, zu dem dieser Nachrichtenbuffer gehört

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

tipc_chan_put_rxbuf()

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

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

[in] chan: Verweis auf einen Kanal, zu dem dieser Nachrichtenbuffer gehört

[in] mb: Zeiger auf einen Nachrichtenbuffer, der freigegeben werden soll

[retval]: None