Trusty fournit des API pour développer deux classes d'applications et de services:
- Applications et services approuvés exécutés sur le processeur TEE
- Applications normales et non approuvées qui s'exécutent sur le processeur principal et utilisent les services fournis par les applications approuvées
L'API Trusty décrit généralement le système de communication inter-processus (IPC) Trusty, y compris les communications avec le monde non sécurisé. Les logiciels exécutés sur le processeur principal peuvent utiliser des API Trusty pour se connecter à des applications et services approuvés et échanger des messages arbitraires avec eux, comme un service réseau sur IP. C'est à l'application de déterminer le format de données et la sémantique de ces messages à l'aide d'un protocole au niveau de l'application. La distribution fiable des messages est garantie par l'infrastructure Trusty sous-jacente (sous la forme de pilotes exécutés sur le processeur principal), et la communication est entièrement asynchrone.
Ports et canaux
Les applications Trusty utilisent des ports pour exposer les points de terminaison de service sous la forme d'un chemin nommé auquel les clients se connectent. Cela permet aux clients d'utiliser un ID de service simple, basé sur une chaîne. La convention d'attribution de noms est de type DNS inversé, par exemple com.google.servicename
.
Lorsqu'un client se connecte à un port, il reçoit un canal pour interagir avec un service. Le service doit accepter une connexion entrante. Lorsqu'il le fait, il reçoit également un canal. En substance, les ports sont utilisés pour rechercher des services, puis la communication s'effectue via une paire de canaux connectés (c'est-à-dire instances de connexion sur un port). Lorsqu'un client se connecte à un port, une connexion symétrique bidirectionnelle est établie. À l'aide de ce chemin full-duplex, les clients et les serveurs peuvent échanger des messages arbitraires jusqu'à ce que l'un des côtés décide de mettre fin à la connexion.
Seules les applications fiables côté sécurisé ou les modules de kernel Trusty peuvent créer des ports. Les applications exécutées sur le côté non sécurisé (dans le monde normal) ne peuvent se connecter qu'aux services publiés par le côté sécurisé.
Selon les exigences, une application approuvée peut être à la fois un client et un serveur. Une application approuvée qui publie un service (en tant que serveur) peut devoir se connecter à d'autres services (en tant que client).
API Handle
Les poignées sont des entiers non signés représentant des ressources telles que des ports et des canaux, semblables aux descripteurs de fichiers sous UNIX. Une fois les poignées créées, elles sont placées dans une table de poignées spécifique à l'application et peuvent être référencées ultérieurement.
Un appelant peut associer des données privées à un gestionnaire à l'aide de la méthode set_cookie()
.
Méthodes de l'API Handle
Les poignées ne sont valides que dans le contexte d'une application. Une application ne doit pas transmettre la valeur d'une poignée à d'autres applications, sauf si elle est explicitement spécifiée. Une valeur de poignée ne doit être interprétée que par comparaison avec l'INVALID_IPC_HANDLE #define,
qu'une application peut utiliser pour indiquer qu'une poignée est non valide ou non définie.
set_cookie()
Associe les données privées fournies par l'appelant à un gestionnaire spécifié.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: tout gestionnaire renvoyé par l'un des appels d'API
[in] cookie
: pointeur vers des données d'espace utilisateur arbitraires dans l'application Trusty
[retval]: NO_ERROR
en cas de réussite, code d'erreur < 0
dans le cas contraire
Cet appel est utile pour gérer les événements lorsqu'ils se produisent plus tard après la création du gestionnaire. Le mécanisme de gestion des événements fournit le handle et son cookie au gestionnaire d'événements.
Vous pouvez attendre des événements à l'aide de l'appel wait()
.
wait()
Attend qu'un événement se produise sur un gestionnaire donné pendant une période spécifiée.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: tout gestionnaire renvoyé par l'un des appels d'API
[sortie] event
: pointeur vers la structure représentant un événement survenu sur ce gestionnaire
[in] timeout_msecs
: valeur de délai avant expiration en millisecondes. Une valeur de -1 correspond à un délai avant expiration infini.
[retval]: NO_ERROR
si un événement valide s'est produit dans un délai spécifié ; ERR_TIMED_OUT
si un délai spécifié s'est écoulé, mais qu'aucun événement ne s'est produit ; < 0
pour les autres erreurs
En cas de réussite (retval == NO_ERROR
), l'appel wait()
remplit une structure uevent_t
spécifiée avec des informations sur l'événement qui s'est produit.
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;
Le champ event
contient une combinaison des valeurs suivantes:
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
: aucun événement n'est en attente. L'appelant doit redémarrer l'attente.
IPC_HANDLE_POLL_ERROR
: une erreur interne non spécifiée s'est produite
IPC_HANDLE_POLL_READY
: dépend du type de poignée, comme suit :
- Pour les ports, cette valeur indique qu'une connexion est en attente.
- Pour les canaux, cette valeur indique qu'une connexion asynchrone (voir
connect()
) a été établie.
Les événements suivants ne sont pertinents que pour les canaux:
IPC_HANDLE_POLL_HUP
: indique qu'une chaîne a été fermée par un pairIPC_HANDLE_POLL_MSG
: indique qu'un message est en attente pour cette chaîneIPC_HANDLE_POLL_SEND_UNBLOCKED
: indique qu'un appelant précédemment bloqué peut tenter d'envoyer à nouveau un message (voir la description desend_msg()
pour en savoir plus)
Un gestionnaire d'événements doit être prêt à gérer une combinaison d'événements spécifiés, car plusieurs bits peuvent être définis en même temps. Par exemple, pour un canal, il est possible d'avoir des messages en attente et une connexion fermée par un pair en même temps.
La plupart des événements sont persistants. Ils persistent tant que la condition sous-jacente persiste (par exemple, tous les messages en attente sont reçus et les requêtes de connexion en attente sont gérées). L'exception concerne l'événement IPC_HANDLE_POLL_SEND_UNBLOCKED
, qui est effacé lors d'une lecture et que l'application n'a qu'une seule chance de gérer.
Vous pouvez détruire les poignées en appelant la méthode close()
.
close()
Détruit la ressource associée au contrôleur spécifié et la supprime de la table de contrôleurs.
long close(uint32_t handle_id);
[in] handle_id
: handle à détruire
[retval]: 0 en cas de réussite, une erreur négative dans le cas contraire
API du serveur
Un serveur commence par créer un ou plusieurs ports nommés représentant ses points de terminaison de service. Chaque port est représenté par une poignée.
Méthodes de l'API Server
port_create()
Crée un port de service nommé.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: nom de la chaîne du port (comme décrit ci-dessus). Ce nom doit être unique dans le système. Les tentatives de création d'un doublon échouent.
[in] num_recv_bufs
: nombre maximal de tampons qu'un canal sur ce port peut préallouer pour faciliter l'échange de données avec le client. Les tampons sont comptés séparément pour les données allant dans les deux sens. Par conséquent, spécifier 1 ici signifie qu'un tampon d'envoi et un tampon de réception sont préalloués. En général, le nombre de tampons requis dépend de l'accord de protocole de niveau supérieur entre le client et le serveur. Ce nombre peut être aussi bas que 1 dans le cas d'un protocole très synchrone (envoyer un message, recevoir une réponse avant d'en envoyer un autre). Toutefois, ce nombre peut être supérieur si le client s'attend à envoyer plusieurs messages avant qu'une réponse ne puisse s'afficher (par exemple, un message en guise de prologue et un autre en tant que commande). Les ensembles de tampons alloués sont par canal. Par conséquent, deux connexions (canaux) distinctes auront des ensembles de tampons distincts.
[in] recv_buf_size
: taille maximale de chaque tampon individuel de l'ensemble de tampons ci-dessus. Cette valeur dépend du protocole et limite efficacement la taille maximale des messages que vous pouvez échanger avec l'homologue.
[in] flags
: combinaison d'indicateurs qui spécifie un comportement de port supplémentaire
Cette valeur doit être une combinaison des valeurs suivantes:
IPC_PORT_ALLOW_TA_CONNECT
: autorise une connexion à partir d'autres applications sécurisées
IPC_PORT_ALLOW_NS_CONNECT
: autorise une connexion depuis un environnement non sécurisé
[retval]: poignée du port créé si elle n'est pas négative ou erreur spécifique si elle l'est
Le serveur interroge ensuite la liste des poignées de port pour les connexions entrantes à l'aide de l'appel wait()
. Lorsqu'il reçoit une requête de connexion indiquée par le bit IPC_HANDLE_POLL_READY
défini dans le champ event
de la structure uevent_t
, le serveur doit appeler accept()
pour terminer l'établissement d'une connexion et créer un canal (représenté par un autre handle) qui peut ensuite être interrogé pour les messages entrants.
accept()
Accepte une connexion entrante et obtient un identifiant de canal.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: poignée représentant le port auquel un client s'est connecté
[out] peer_uuid
: Pointeur vers une structure uuid_t
à remplir avec l'UUID de l'application cliente qui se connecte. Il est défini sur "0" si la connexion provient du monde non sécurisé.
[retval]: poignée d'un canal (si non négatif) sur lequel le serveur peut échanger des messages avec le client (ou un code d'erreur dans le cas contraire)
API client
Cette section contient les méthodes de l'API cliente.
Méthodes de l'API cliente
connect()
Initie une connexion à un port spécifié par nom.
long connect(const char *path, uint flags);
[in] path
: nom d'un port publié par une application Trusty
[in] flags
: spécifie un comportement supplémentaire facultatif
[retval]: poignée d'un canal sur lequel des messages peuvent être échangés avec le serveur ; erreur si la valeur est négative
Si aucun flags
n'est spécifié (le paramètre flags
est défini sur 0), l'appel de connect()
lance une connexion synchrone à un port spécifié qui renvoie immédiatement une erreur si le port n'existe pas et crée un blocage jusqu'à ce que le serveur accepte une connexion.
Ce comportement peut être modifié en spécifiant une combinaison de deux valeurs, décrites ci-dessous:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
: force un appel connect()
à attendre si le port spécifié n'existe pas immédiatement lors de l'exécution, au lieu d'échouer immédiatement.
IPC_CONNECT_ASYNC
: si défini, lance une connexion asynchrone. Une application doit interroger le handle renvoyé en appelant wait()
pour un événement de fin de connexion indiqué par le bit IPC_HANDLE_POLL_READY
défini dans le champ d'événement de la structure uevent_t
avant de démarrer le fonctionnement normal.
API Messaging
Les appels de l'API Messaging permettent d'envoyer et de lire des messages via une connexion (canal) précédemment établie. Les appels de l'API Messaging sont les mêmes pour les serveurs et les clients.
Un client reçoit un handle de canal en émettant un appel connect()
, et un serveur obtient un handle de canal à partir d'un appel accept()
, comme décrit ci-dessus.
Structure d'un message Trusty
Comme indiqué ci-dessous, les messages échangés par l'API Trusty ont une structure minimale, laissant au serveur et au client le soin de s'entendre sur la sémantique des contenus réels:
/* * 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;
Un message peut être composé d'un ou de plusieurs tampons non contigus représentés par un tableau de structures iovec_t
. Trusty effectue des lectures et des écritures scatter-gather sur ces blocs à l'aide du tableau iov
. Le contenu des tampons pouvant être décrit par le tableau iov
est complètement arbitraire.
Méthodes de l'API Messaging
send_msg()
Envoie un message sur un canal spécifié.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: poignée de la chaîne sur laquelle envoyer le message
[in] msg
: pointeur vers le ipc_msg_t structure
décrivant le message
[retval]: nombre total d'octets envoyés en cas de réussite. En cas d'échec, une erreur négative est renvoyée.
Si le client (ou le serveur) tente d'envoyer un message via le canal et qu'il n'y a pas d'espace dans la file d'attente de messages de l'homologue de destination, le canal peut passer à un état d'envoi bloqué (cela ne devrait jamais se produire pour un protocole de requête/réponse synchrone simple, mais cela peut se produire dans des cas plus complexes), ce qui est indiqué par le renvoi d'un code d'erreur ERR_NOT_ENOUGH_BUFFER
.
Dans ce cas, l'appelant doit attendre que l'homologue libère de l'espace dans sa file d'attente de réception en récupérant les messages de traitement et de mise à la retraite, indiqués par le bit IPC_HANDLE_POLL_SEND_UNBLOCKED
défini dans le champ event
de la structure uevent_t
renvoyée par l'appel wait()
.
get_msg()
Récupère les métadonnées du prochain message d'une file d'attente de messages entrants
d'une chaîne spécifiée.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: poignée de la chaîne sur laquelle un nouveau message doit être récupéré
[out] msg_info
: structure des informations du message décrite comme suit:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Chaque message se voit attribuer un ID unique dans l'ensemble des messages en attente, et la longueur totale de chaque message est renseignée. Si le protocole est configuré et autorisé, plusieurs messages en attente (ouverts) peuvent être envoyés simultanément pour un canal donné.
[retval]: NO_ERROR
en cas de réussite, une erreur négative dans le cas contraire
read_msg()
Lit le contenu du message avec l'ID spécifié à partir du décalage spécifié.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: poignée du canal à partir duquel lire le message
[in] msg_id
: ID du message à lire
[in] offset
: décalage dans le message à partir duquel commencer la lecture
[sortie] msg
: pointeur vers la structure ipc_msg_t
décrivant un ensemble de tampons dans lesquels stocker les données des messages entrants
[retval]: nombre total d'octets stockés dans les tampons msg
en cas de réussite. En cas d'échec, une erreur négative est renvoyée.
La méthode read_msg
peut être appelée plusieurs fois à partir d'un décalage différent (pas nécessairement séquentiel) si nécessaire.
put_msg()
Retire un message avec un ID spécifié.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: identifiant du canal sur lequel le message est arrivé
[in] msg_id
: ID du message à supprimer
[retval]: NO_ERROR
en cas de réussite, une erreur négative dans le cas contraire
Le contenu d'un message n'est plus accessible une fois qu'il a été supprimé et que le tampon qu'il occupait a été libéré.
API File Descriptor
L'API File Descriptor inclut les appels read()
, write()
et ioctl()
. Tous ces appels peuvent fonctionner sur un ensemble prédéfini (statique) de descripteurs de fichiers traditionnellement représentés par de petits nombres. Dans l'implémentation actuelle, l'espace de descripteur de fichier est distinct de l'espace de poignée IPC. L'API File Descriptor de Trusty est semblable à une API basée sur un descripteur de fichier traditionnel.
Par défaut, trois descripteurs de fichiers prédéfinis (standards et bien connus) sont disponibles:
- 0 : entrée standard. L'implémentation par défaut de l'entrée standard
fd
est une opération sans effet (car les applications approuvées ne sont pas censées disposer d'une console interactive). Par conséquent, la lecture, l'écriture ou l'appel deioctl()
surfd
0 doit renvoyer une erreurERR_NOT_SUPPORTED
. - 1 : sortie standard. Les données écrites sur la sortie standard peuvent être acheminées (selon le niveau de débogage de LK) vers UART et/ou un journal de mémoire disponible côté non sécurisé, en fonction de la plate-forme et de la configuration. Les journaux et messages de débogage non critiques doivent être placés dans la sortie standard. Les méthodes
read()
etioctl()
ne sont pas des opérations et doivent renvoyer une erreurERR_NOT_SUPPORTED
. - 2 : erreur-type. Les données écrites dans l'erreur standard doivent être acheminées vers le journal UART ou de mémoire disponible côté non sécurisé, en fonction de la plate-forme et de la configuration. Il est recommandé de n'écrire que des messages critiques dans l'erreur standard, car ce flux est très susceptible de ne pas être limité. Les méthodes
read()
etioctl()
ne sont pas des opérations et doivent renvoyer une erreurERR_NOT_SUPPORTED
.
Bien que cet ensemble de descripteurs de fichiers puisse être étendu pour implémenter davantage de fds
(pour implémenter des extensions spécifiques à la plate-forme), l'extension des descripteurs de fichiers doit être effectuée avec prudence. L'extension des descripteurs de fichiers est susceptible de créer des conflits et n'est généralement pas recommandée.
Méthodes de l'API File Descriptor
read()
Tente de lire jusqu'à count
octets de données à partir d'un descripteur de fichier spécifié.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: descripteur de fichier à partir duquel lire
[sortie] buf
: pointeur vers un tampon dans lequel stocker des données
[in] count
: nombre maximal d'octets à lire
[retval]: nombre d'octets lus renvoyés. En cas d'erreur, une valeur négative est renvoyée.
write()
Écrit jusqu'à count
octets de données dans le descripteur de fichier spécifié.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: descripteur de fichier dans lequel écrire
[out] buf
: pointeur vers les données à écrire
[in] count
: nombre maximal d'octets à écrire
[retval]: nombre d'octets écrits renvoyés. En cas d'erreur, une valeur négative est renvoyée.
ioctl()
Invoque une commande ioctl
spécifiée pour un descripteur de fichier donné.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: descripteur de fichier sur lequel appeler ioctl()
[in] cmd
: commande ioctl
[entrée/sortie] args
: pointeur vers les arguments ioctl()
API diverses
Méthodes de l'API "Divers"
gettime()
Renvoie l'heure système actuelle (en nanosecondes).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: dépend de la plate-forme ; transmettez zéro par défaut
[in] flags
: réservé, doit être nul
[sortie] time
: pointeur vers une valeur int64_t
dans laquelle stocker l'heure actuelle
[retval]: NO_ERROR
en cas de réussite, sinon une erreur négative
nanosleep()
Suspend l'exécution de l'application appelante pendant une période spécifiée et la reprend après cette période.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: réservé, doit être nul
[in] flags
: réservé, doit être nul
[in] sleep_time
: temps de veille en nanosecondes
[retval]: NO_ERROR
en cas de réussite, sinon une erreur négative
Exemple de serveur d'applications approuvé
L'application exemple suivante illustre l'utilisation des API ci-dessus. L'exemple crée un service "écho" qui gère plusieurs connexions entrantes et renvoie à l'appelant tous les messages qu'il reçoit des clients provenant du côté sécurisé ou non sécurisé.
#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; }
La méthode run_end_to_end_msg_test()
envoie 10 000 messages de manière asynchrone au service "echo" et gère les réponses.
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; }
API et applications du monde non sécurisé
Un ensemble de services Trusty, publiés à partir du côté sécurisé et marqués de l'attribut IPC_PORT_ALLOW_NS_CONNECT
, sont accessibles aux programmes du noyau et de l'espace utilisateur exécutés sur le côté non sécurisé.
L'environnement d'exécution côté non sécurisé (noyau et espace utilisateur) est radicalement différent de l'environnement d'exécution côté sécurisé. Par conséquent, au lieu d'une seule bibliothèque pour les deux environnements, il existe deux ensembles d'API différents. Dans le noyau, l'API client est fournie par le pilote de noyau trusty-ipc et enregistre un nœud de périphérique de caractères pouvant être utilisé par les processus d'espace utilisateur pour communiquer avec les services exécutés côté sécurisé.
API client IPC Trusty dans l'espace utilisateur
La bibliothèque d'API client IPC Trusty de l'espace utilisateur est une couche mince au-dessus du nœud de l'appareil fd
.
Un programme d'espace utilisateur démarre une session de communication en appelant tipc_connect()
, ce qui initialise une connexion à un service Trusty spécifié. En interne, l'appel tipc_connect()
ouvre un nœud d'appareil spécifié pour obtenir un descripteur de fichier et appelle un appel TIPC_IOC_CONNECT ioctl()
avec le paramètre argp
pointant vers une chaîne contenant un nom de service auquel se connecter.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Le descripteur de fichier obtenu ne peut être utilisé que pour communiquer avec le service pour lequel il a été créé. Le descripteur de fichier doit être fermé en appelant tipc_close()
lorsque la connexion n'est plus nécessaire.
Le descripteur de fichier obtenu par l'appel tipc_connect()
se comporte comme un nœud de périphérique de caractères typique. Le descripteur de fichier:
- Peut être activé en mode non bloquant si nécessaire
- Peut être écrit à l'aide d'un appel
write()
standard pour envoyer des messages à l'autre côté - Peut être interrogé (à l'aide d'appels
poll()
ouselect()
) pour connaître la disponibilité des messages entrants en tant que descripteur de fichier standard - Peut être lu pour récupérer les messages entrants
Un appelant envoie un message au service Trusty en exécutant un appel d'écriture pour l'fd
spécifiée. Toutes les données transmises à l'appel write()
ci-dessus sont transformées en message par le pilote trusty-ipc. Le message est envoyé au côté sécurisé, où les données sont gérées par le sous-système IPC du noyau Trusty, acheminées vers la destination appropriée et transmises à une boucle d'événements d'application en tant qu'événement IPC_HANDLE_POLL_MSG
sur un gestionnaire de canal particulier. En fonction du protocole spécifique au service, le service Trusty peut envoyer un ou plusieurs messages de réponse qui sont renvoyés vers le côté non sécurisé et placés dans la file d'attente de messages du descripteur de fichier de canal approprié pour être récupérés par l'appel read()
de l'application de l'espace utilisateur.
tipc_connect()
Ouvre un nœud d'appareil tipc
spécifié et lance une connexion à un service Trusty spécifié.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: chemin d'accès au nœud de l'appareil IPC Trusty à ouvrir
[in] srv_name
: nom d'un service Trusty publié auquel se connecter
[retval]: descripteur de fichier valide en cas de réussite, -1 dans le cas contraire.
tipc_close()
Ferme la connexion au service Trusty spécifié par un descripteur de fichier.
int tipc_close(int fd);
[in] fd
: descripteur de fichier précédemment ouvert par un appel tipc_connect()
API client Kernel Trusty IPC
L'API client IPC Trusty du kernel est disponible pour les pilotes du kernel. L'API Trusty IPC de l'espace utilisateur est implémentée sur cette API.
En général, l'utilisation typique de cette API consiste en la création d'un objet struct tipc_chan
par un appelant à l'aide de la fonction tipc_create_channel()
, puis à l'aide de l'appel tipc_chan_connect()
pour établir une connexion au service IPC Trusty exécuté côté sécurisé. La connexion au côté distant peut être interrompue en appelant tipc_chan_shutdown()
, suivi de tipc_chan_destroy()
pour nettoyer les ressources.
Lorsqu'un appelant reçoit une notification (via le rappel handle_event()
) indiquant qu'une connexion a été établie, il procède comme suit:
- Obtient un tampon de message à l'aide de l'appel
tipc_chan_get_txbuf_timeout()
- Rédige un message, et
- Met le message en file d'attente à l'aide de la méthode
tipc_chan_queue_msg()
pour l'envoi à un service Trusty (côté sécurisé), auquel le canal est connecté.
Une fois la mise en file d'attente réussie, l'appelant doit oublier le tampon de message, car il est finalement renvoyé dans la pool de tampons libres après avoir été traité par le côté distant (pour être réutilisé plus tard, pour d'autres messages). L'utilisateur n'a besoin d'appeler tipc_chan_put_txbuf()
que s'il ne parvient pas à mettre en file d'attente un tel tampon ou s'il n'est plus nécessaire.
Un utilisateur d'API reçoit des messages de la partie distante en gérant un rappel de notification handle_msg()
(appelé dans le contexte de la file de travail rx
trusty-ipc) qui fournit un pointeur vers un tampon rx
contenant un message entrant à traiter.
L'implémentation du rappel handle_msg()
devrait renvoyer un pointeur vers un struct tipc_msg_buf
valide.
Il peut être identique au tampon de message entrant s'il est géré localement et n'est plus nécessaire. Il peut également s'agir d'un nouveau tampon obtenu par un appel tipc_chan_get_rxbuf()
si le tampon entrant est mis en file d'attente pour un traitement ultérieur. Un tampon rx
dissocié doit être suivi et éventuellement libéré à l'aide d'un appel tipc_chan_put_rxbuf()
lorsqu'il n'est plus nécessaire.
Méthodes de l'API cliente Kernel Trusty IPC
tipc_create_channel()
Crée et configure une instance d'un canal IPC Trusty pour un appareil trusty-ipc particulier.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: pointeur vers le trusty-ipc pour lequel le canal de l'appareil est créé
[in] ops
: pointeur vers un struct tipc_chan_ops
, avec les rappels spécifiques à l'appelant remplis
[in] cb_arg
: pointeur vers les données transmises aux rappels tipc_chan_ops
[retval]: pointeur vers une nouvelle instance de struct tipc_chan
en cas de réussite, ERR_PTR(err)
sinon
En général, un appelant doit fournir deux rappels qui sont appelés de manière asynchrone lorsque l'activité correspondante se produit.
L'événement void (*handle_event)(void *cb_arg, int event)
est appelé pour informer un appelant d'un changement d'état de canal.
[in] cb_arg
: pointeur vers les données transmises à un appel tipc_create_channel()
[in] event
: événement qui peut prendre l'une des valeurs suivantes:
TIPC_CHANNEL_CONNECTED
: indique une connexion réussie à l'autre extrémitéTIPC_CHANNEL_DISCONNECTED
: indique que le côté distant a refusé la nouvelle demande de connexion ou demandé la déconnexion du canal précédemment connecté.TIPC_CHANNEL_SHUTDOWN
: indique que le côté distant s'arrête, ce qui met fin définitivement à toutes les connexions
Le rappel struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
est appelé pour indiquer qu'un nouveau message a été reçu sur un canal spécifié:
- [in]
cb_arg
: pointeur vers les données transmises à l'appeltipc_create_channel()
- [in]
mb
: pointeur vers unstruct tipc_msg_buf
décrivant un message entrant - [retval]: l'implémentation du rappel doit renvoyer un pointeur vers un
struct tipc_msg_buf
qui peut être le même pointeur reçu en tant que paramètremb
si le message est géré localement et n'est plus nécessaire (ou il peut s'agir d'un nouveau tampon obtenu par l'appeltipc_chan_get_rxbuf()
).
tipc_chan_connect()
Lance une connexion au service IPC Trusty spécifié.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: pointeur vers une chaîne renvoyée par l'appel tipc_create_chan()
[in] port
: pointeur vers une chaîne contenant le nom du service auquel se connecter
[retval]: 0 en cas de réussite, une erreur négative dans le cas contraire
L'appelant est averti lorsqu'une connexion est établie en recevant un rappel handle_event
.
tipc_chan_shutdown()
Met fin à une connexion au service IPC Trusty précédemment initiée par un appel tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: pointeur vers un canal renvoyé par un appel tipc_create_chan()
tipc_chan_destroy()
Détruit un canal IPC Trusty spécifié.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: pointeur vers un canal renvoyé par l'appel tipc_create_chan()
tipc_chan_get_txbuf_timeout()
Obtient un tampon de message pouvant être utilisé pour envoyer des données sur un canal spécifié. Si la mémoire tampon n'est pas immédiatement disponible, l'appelant peut être bloqué pendant le délai avant expiration spécifié (en millisecondes).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: pointeur vers la chaîne à laquelle mettre en file d'attente un message
[in] chan
: délai maximal d'attente jusqu'à ce que le tampon tx
soit disponible
[retval]: tampon de message valide en cas de réussite, ERR_PTR(err)
en cas d'erreur
tipc_chan_queue_msg()
Met en file d'attente un message à envoyer via les canaux IPC Trusty spécifiés.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: pointeur vers la chaîne à laquelle mettre le message en file d'attente
[in] mb:
Pointeur vers le message à mettre en file d'attente (obtenu par un appel tipc_chan_get_txbuf_timeout()
)
[retval]: 0 en cas de réussite, une erreur négative dans le cas contraire
tipc_chan_put_txbuf()
Libère le tampon de message Tx
spécifié précédemment obtenu par un appel tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: pointeur vers le canal auquel appartient ce tampon de messages
[in] mb
: pointeur vers le tampon de messages à libérer
[retval]: None
tipc_chan_get_rxbuf()
Obtient un nouveau tampon de message pouvant être utilisé pour recevoir des messages via le canal spécifié.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: pointeur vers un canal auquel appartient ce tampon de messages
[retval]: tampon de message valide en cas de réussite, ERR_PTR(err)
en cas d'erreur
tipc_chan_put_rxbuf()
Libère un tampon de message spécifié précédemment obtenu par un appel tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: pointeur vers un canal auquel appartient ce tampon de messages
[in] mb
: pointeur vers un tampon de message à libérer
[retval]: None