Vertrauenswürdige API-Referenz

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

  • 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 Interprozesskommunikationssystem (IPC) von Trusty, einschließlich der Kommunikation mit der nicht sicheren Welt. Software, die auf dem Hauptprozessor läuft, kann Trusty APIs verwenden, um sich mit vertrauenswürdigen Anwendungen/Diensten zu verbinden 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 laufen) garantiert, und die Kommunikation ist vollständig asynchron.

Häfen und Kanäle

Ports werden von Trusty-Anwendungen verwendet, um Dienstendpunkte in Form eines benannten Pfads bereitzustellen, mit dem sich Clients verbinden. Dies ergibt eine einfache, Zeichenfolgen-basierte Dienst-ID, die von Clients verwendet werden kann. Die Namenskonvention ist eine Namensgebung 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 wenn dies der Fall ist, erhält er ebenfalls einen Kanal. Im Wesentlichen werden Ports verwendet, um Dienste nachzuschlagen, und dann erfolgt die Kommunikation über ein Paar verbundener Kanäle (dh Verbindungsinstanzen an einem Port). Wenn sich ein Client mit einem Port verbindet, wird eine symmetrische, bidirektionale Verbindung hergestellt. Über diesen Vollduplex-Pfad können Clients und Server beliebige Nachrichten austauschen, bis beide Seiten entscheiden, die Verbindung abzubrechen.

Nur vertrauenswürdige Anwendungen auf der sicheren Seite oder Trusty-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 Bedarf kann eine vertrauenswürdige Anwendung gleichzeitig Client und Server sein. Eine vertrauenswürdige Anwendung, die einen Dienst (als Server) veröffentlicht, muss möglicherweise eine Verbindung zu anderen Diensten (als Client) herstellen.

API verarbeiten

Handles sind Ganzzahlen ohne Vorzeichen, 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 nur dann an andere Anwendungen weitergeben, wenn dies ausdrücklich angegeben ist. Ein Handle-Wert sollte nur interpretiert werden, indem er mit INVALID_IPC_HANDLE #define, was eine Anwendung als Hinweis darauf verwenden kann, dass ein Handle ungültig oder nicht gesetzt ist.

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

long set_cookie(uint32_t handle, void *cookie)

[in] handle : Jeder Handle, der 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, sonst < 0 Fehlercode

Dieser Aufruf ist nützlich für die Behandlung von Ereignissen, wenn sie zu einem späteren Zeitpunkt auftreten, nachdem das Handle erstellt wurde. Der Event-Handling-Mechanismus liefert das Handle und sein Cookie zurück an den Event-Handler.

Auf Handles kann mithilfe des Aufrufs wait() 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 : Jeder Handle, der 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 ein gültiges Ereignis innerhalb eines angegebenen Timeout-Intervalls 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 wait() Aufruf 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 – tatsächlich stehen keine Ereignisse an, der Anrufer sollte das Warten neu starten

IPC_HANDLE_POLL_ERROR - ein nicht näher bezeichneter interner Fehler ist aufgetreten

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

  • Für Ports gibt dieser Wert an, dass eine ausstehende Verbindung besteht
  • Bei Kanälen zeigt dieser Wert an, dass eine asynchrone Verbindung (siehe connect() ) aufgebaut 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 es eine anhängige Nachricht für diesen Kanal gibt
  • IPC_HANDLE_POLL_SEND_UNBLOCKED – zeigt an, dass ein zuvor beim Senden blockierter Anrufer versuchen kann, eine Nachricht erneut zu senden (siehe die Beschreibung von send_msg() für Details)

Ein Event-Handler sollte darauf vorbereitet sein, eine Kombination bestimmter Events zu handhaben, da mehrere Bits gleichzeitig gesetzt werden können. Beispielsweise ist es für einen Kanal möglich, anstehende Nachrichten zu haben und gleichzeitig eine Verbindung von einem Peer geschlossen zu haben.

Die meisten Ereignisse sind klebrig. Sie bleiben bestehen, solange die zugrunde liegende Bedingung besteht (z. B. alle anstehenden Nachrichten empfangen und anstehende Verbindungsanforderungen bearbeitet werden). Die Ausnahme ist der Fall des IPC_HANDLE_POLL_SEND_UNBLOCKED Ereignisses, das bei einem Lesevorgang gelöscht wird und die Anwendung nur eine Chance hat, es zu handhaben.

Handles können zerstört werden, indem die Methode close() aufgerufen wird.

nah dran()

Zerstört die Ressource, die dem angegebenen Handle zugeordnet ist, 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 beginnt mit der Erstellung eines oder mehrerer benannter Ports , die seine Dienstendpunkte darstellen. Jeder Port wird durch ein Handle repräsentiert.

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 String-Name des Ports (wie oben beschrieben). Dieser Name sollte systemweit eindeutig sein; Versuche, ein Duplikat zu erstellen, schlagen fehl.

[in] num_recv_bufs : Die maximale Anzahl von Puffern, die ein Kanal an diesem Port vorbelegen kann, um den Datenaustausch mit dem Client zu erleichtern. Puffer werden für Daten, die in beide Richtungen gehen, separat gezählt, sodass die Angabe von 1 hier bedeuten würde, dass 1 Sende- und 1 Empfangspuffer vorab zugewiesen werden. 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 nur 1 betragen. Aber die Zahl kann 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 haben würden.

[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 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 – erlaubt 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 IPC_HANDLE_POLL_READY Bit angezeigt wird, das im event der uevent_t Struktur gesetzt ist, 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 .

annehmen()

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 sich ein Client verbunden hat

[out] peer_uuid : Zeiger auf eine uuud_t Struktur, die mit der UUID der verbindenden Client-Anwendung gefüllt werden soll. Es wird vollständig auf Nullen gesetzt, wenn die Verbindung aus der nicht sicheren Welt stammt

[retval]: Handle auf einen Kanal (falls nicht negativ), auf dem der Server Nachrichten mit dem Client austauschen kann (oder andernfalls einen 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 Ports, der von einer Trusty-Anwendung veröffentlicht wird

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

[retval]: Handle auf 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 geändert werden, indem eine Kombination aus zwei Werten angegeben wird, die unten beschrieben werden:

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

IPC_CONNECT_WAIT_FOR_PORT – zwingt einen connect() -Aufruf zu warten, wenn der angegebene Port bei der Ausführung nicht sofort existiert, anstatt sofort fehlzuschlagen.

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

Messaging-API

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

Ein Client erhält eine Kennung für einen Kanal, indem er einen connect() -Aufruf ausgibt, und ein Server erhält eine Kanalkennung 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, sodass 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 Scatter-Gather-Lese- und -Schreibvorgänge in diesen Blöcken mithilfe des iov Arrays durch. 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 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 Nachrichtenwarteschlange des Ziel-Peers kein Platz vorhanden ist, tritt der Kanal möglicherweise in einen Sendeblockierzustand ein (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 Behandlungs- und Zurückziehungsnachrichten abruft, angezeigt durch das IPC_HANDLE_POLL_SEND_UNBLOCKED Bit, das im event der uevent_t Struktur gesetzt ist, die vom wait() Aufruf zurückgegeben wird.

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 wie folgt beschrieben:

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

Jeder Nachricht wird über den Satz ausstehender Nachrichten hinweg eine eindeutige ID zugewiesen, und die Gesamtlänge jeder Nachricht wird eingetragen. Wenn es vom Protokoll konfiguriert und zugelassen wird, 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, aus dem die Nachricht gelesen werden soll

[in] msg_id : ID der zu lesenden Nachricht

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

[in] msg : Zeiger auf die ipc_msg_t Struktur, die einen Satz von Puffern beschreibt, in denen eingehende Nachrichtendaten gespeichert werden

[retval]: Gesamtzahl der bei Erfolg in den dst Puffern gespeicherten Bytes; andernfalls ein negativer Fehler

Die read_msg Methode kann 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 zurückgezogenen Nachricht

[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 read() -, write() - und ioctl() -Aufrufe. 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 von dem IPC-Handle-Raum getrennt. Die Dateideskriptor-API in Trusty ähnelt einer herkömmlichen dateideskriptorbasierten API.

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

  • 0 - Standardeingabe. Die Standardimplementierung der Standardeingabe fd ist ein No-Op (da von vertrauenswürdigen Anwendungen nicht erwartet wird, dass sie eine interaktive Konsole haben), sodass das Lesen, Schreiben oder Aufrufen ioctl() auf fd 0 einen ERR_NOT_SUPPORTED -Fehler zurückgeben sollte.
  • 1 - Standardausgabe. Auf die Standardausgabe geschriebene Daten können (je nach LK-Debug-Level) je nach Plattform und Konfiguration an UART und/oder ein auf der nicht sicheren Seite verfügbares Speicherprotokoll geleitet werden. Unkritische Debug-Protokolle und -Meldungen sollten in die Standardausgabe gehen. Die read() und ioctl() Methoden 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 auf der nicht sicheren Seite verfügbar ist. Es wird empfohlen, nur kritische Meldungen in den Standardfehler zu schreiben, da dieser Stream sehr wahrscheinlich ungedrosselt ist. Die read() und ioctl() Methoden sind no-ops und sollten einen ERR_NOT_SUPPORTED -Fehler zurückgeben.

Obwohl dieser Satz von Dateideskriptoren erweitert werden kann, um mehr fds zu implementieren (um plattformspezifische Erweiterungen zu implementieren), muss das Erweitern von Dateideskriptoren mit Vorsicht geübt werden. Das Erweitern von Dateideskriptoren ist anfällig für Konflikte und wird im Allgemeinen nicht empfohlen.

Methoden in der Dateideskriptor-API

lesen()

Versucht zu lesen, um Datenbytes aus einem angegebenen Dateideskriptor zu count .

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 zu lesender Bytes

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

schreiben()

Schreibt bis zu count Datenbytes 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 zu schreibende Daten

[in] count : Maximale Anzahl zu schreibender 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()

[in] cmd : Der ioctl Befehl

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

Verschiedenes API

Methoden in der Verschiedenes-API

Zeit bekommen()

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

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

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

[in] flags : Reserviert, sollte Null sein

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

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

Nanoschlaf()

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

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 erhält, die von der sicheren oder nicht sicheren Seite stammen, an den Aufrufer zurückspiegelt.

#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 asynchron 10.000 Nachrichten 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;
}

APIs und Anwendungen der nicht sicheren Welt

Eine Reihe von Trusty-Diensten, die von der sicheren Seite 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 Benutzerbereich) unterscheidet sich drastisch von der Ausführungsumgebung auf der sicheren Seite. Daher gibt es statt einer einzelnen Bibliothek für beide Umgebungen zwei verschiedene Sätze von APIs. Im Kernel wird die Client-API vom Trusty-ipc-Kerneltreiber bereitgestellt und registriert einen Zeichengeräteknoten, der von Benutzerraumprozessen verwendet werden kann, um mit Diensten zu kommunizieren, die auf der sicheren Seite ausgeführt werden.

Benutzerbereich Trusty IPC Client API

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

Ein User-Space-Programm startet eine Kommunikationssitzung, indem es tipc_connect() und eine Verbindung zu einem bestimmten Trusty-Dienst initialisiert. Intern öffnet der tipc_connect() Aufruf einen angegebenen Geräteknoten, um einen Dateideskriptor zu erhalten, und ruft einen TIPC_IOC_CONNECT ioctl() Aufruf auf, wobei der argp Parameter 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 von tipc_close() geschlossen werden, wenn die Verbindung nicht mehr benötigt wird.

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

  • Kann bei Bedarf in den nicht blockierenden Modus geschaltet werden
  • Kann mit einem Standard- write() Aufruf beschrieben werden, um Nachrichten an die andere Seite zu senden
  • Kann abgefragt werden (unter Verwendung von poll() Aufrufen oder select() Aufrufen) für die Verfügbarkeit eingehender Nachrichten als regulärer Dateideskriptor
  • Kann gelesen werden, um eingehende Nachrichten abzurufen

Ein Aufrufer sendet eine Nachricht an den Trusty-Dienst, indem er einen Schreibaufruf für das angegebene fd ausführt. Alle Daten, die an den obigen Aufruf von write() übergeben werden, werden vom Treiber trusty-ipc in eine Nachricht umgewandelt. Die Nachricht wird an die sichere Seite geliefert, wo die Daten vom IPC-Subsystem im Trusty-Kernel verarbeitet und an das richtige Ziel geleitet und als IPC_HANDLE_POLL_MSG -Ereignis auf einem bestimmten Kanalhandle an eine App-Ereignisschleife geliefert werden. Abhängig von dem bestimmten, dienstspezifischen Protokoll kann der Trusty-Dienst eine oder mehrere Antwortnachrichten senden, die an die nicht sichere Seite zurückgeliefert und in die entsprechende Kanaldatei-Deskriptor-Nachrichtenwarteschlange gestellt werden, um von der Benutzerbereichsanwendung read() zu werden. read() anrufen.

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, ansonsten -1.

tipc_close()

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

int tipc_close(int fd);

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

Kernel Trusty IPC-Client-API

Die Kernel Trusty IPC Client API ist für Kerneltreiber verfügbar. Die Trusty IPC-API des Benutzerraums wird auf dieser API implementiert.

Im Allgemeinen besteht die typische Verwendung dieser API darin, dass ein Aufrufer ein struct tipc_chan Objekt mithilfe der Funktion tipc_create_channel() 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 entfernten Seite kann beendet werden, indem tipc_chan_shutdown() gefolgt von tipc_chan_destroy() wird, um Ressourcen zu bereinigen.

Nach dem Erhalt einer Benachrichtigung (über den Callback handle_event() ), dass eine Verbindung erfolgreich hergestellt wurde, führt ein Aufrufer Folgendes aus:

  • Ruft einen Nachrichtenpuffer mit dem Aufruf tipc_chan_get_txbuf_timeout()
  • Verfasst eine Nachricht, und
  • Stellt die Nachricht mit der Methode tipc_chan_queue_msg() in die Warteschlange für die Zustellung an einen Trusty-Dienst (auf der sicheren Seite), mit dem der Kanal verbunden ist

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

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

Es wird erwartet, dass die Callback-Implementierung handle_msg() einen Zeiger auf eine gültige struct tipc_msg_buf . Er kann derselbe sein wie der Eingangsnachrichtenpuffer, wenn er lokal gehandhabt und nicht mehr benötigt wird. Alternativ kann es ein neuer Puffer sein, der durch einen tipc_chan_get_rxbuf() Aufruf erhalten wird, wenn der eingehende Puffer zur weiteren Verarbeitung in die Warteschlange gestellt wird. Ein abgetrennter 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 Callbacks

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

[retval]: Zeiger auf eine neu erstellte Instanz von struct tipc_chan bei Erfolg, sonst 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 Kanalzustandsänderung zu benachrichtigen.

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

[in] event : Ein Ereignis, das einer der folgenden Werte sein kann:

  • TIPC_CHANNEL_CONNECTED – zeigt eine erfolgreiche Verbindung zur entfernten Seite an
  • TIPC_CHANNEL_DISCONNECTED – zeigt an, dass die entfernte Seite die neue Verbindungsanforderung verweigert oder die Trennung für den zuvor verbundenen Kanal angefordert hat
  • TIPC_CHANNEL_SHUTDOWN - zeigt an, dass die Remote-Seite heruntergefahren wird, wodurch alle Verbindungen dauerhaft beendet werden

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

  • [in] cb_arg : Zeiger auf Daten, die an den Aufruf tipc_create_channel()
  • [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 , der derselbe Zeiger sein kann, der als mb -Parameter empfangen wird, wenn die Nachricht lokal behandelt wird und nicht mehr benötigt wird (oder es kann ein neuer Puffer sein, der von der tipc_chan_get_rxbuf() aufrufen)

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 ein handle_event Rückruf empfangen wird.

tipc_chan_shutdown()

Beendet eine zuvor durch einen tipc_chan_connect() Aufruf initiierte Verbindung zum Trusty IPC-Dienst.

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : Zeiger auf einen Kanal, der von einem Aufruf von tipc_create_chan() 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, kann der Anrufer für das angegebene Zeitlimit (in Millisekunden) blockiert werden.

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

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

[in] chan : Maximales Timeout, um zu warten, bis der tx Puffer 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 Nachricht zur Warteschlange (erhalten durch einen Aufruf von 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 tipc_chan_get_txbuf_timeout() Aufruf erhalten 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 von tipc_chan_get_rxbuf() erhalten 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