Trusty fournit des API pour développer deux classes d'applications/services :
- Applications ou services de confiance qui s'exécutent sur le processeur TEE
- Applications normales/non approuvées qui s'exécutent sur le processeur principal et utilisent les services fournis par des 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/services de confiance et échanger des messages arbitraires avec eux, tout comme un service réseau sur IP. Il appartient à 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 livraison 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 complètement asynchrone.
Ports et canaux
Les ports sont utilisés par les applications Trusty pour exposer les points de terminaison de service sous la forme d'un chemin nommé auquel les clients se connectent. Cela donne un ID de service simple, basé sur une chaîne, que les clients peuvent utiliser. La convention de dénomination est de type DNS inversé, par exemple com.google.servicename
.
Lorsqu'un client se connecte à un port, le client reçoit un canal pour interagir avec un service. Le service doit accepter une connexion entrante et, lorsqu'il le fait, il reçoit également un canal. Essentiellement, les ports sont utilisés pour rechercher des services, puis la communication se produit sur une paire de canaux connectés (c'est-à-dire, des instances de connexion sur un port). Lorsqu'un client se connecte à un port, une connexion symétrique bidirectionnelle est établie. En utilisant ce chemin en duplex intégral, les clients et les serveurs peuvent échanger des messages arbitraires jusqu'à ce que l'un ou l'autre des deux côtés décide de rompre la connexion.
Seules les applications sécurisées sécurisées ou les modules de noyau Trusty peuvent créer des ports. Les applications exécutées du 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 besoins, une application de confiance peut être à la fois client et serveur. Une application approuvée qui publie un service (en tant que serveur) peut avoir besoin de se connecter à d'autres services (en tant que client).
Gérer l'API
Les descripteurs sont des entiers non signés représentant des ressources telles que des ports et des canaux, similaires 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 handle en utilisant la méthode set_cookie()
.
Méthodes dans l'API Handle
Les descripteurs ne sont valides que dans le contexte d'une application. Une application ne doit pas transmettre la valeur d'un descripteur à d'autres applications, sauf indication contraire. Une valeur de descripteur ne doit être interprétée qu'en la comparant avec INVALID_IPC_HANDLE #define,
qu'une application peut utiliser comme indication qu'un descripteur est invalide ou non défini.
set_cookie()
Associe les données privées fournies par l'appelant à un handle spécifié.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: Tout handle renvoyé par l'un des appels d'API
[in] cookie
: Pointeur vers des données arbitraires de l'espace utilisateur dans l'application Trusty
[retval] : NO_ERROR
en cas de succès, < 0
code d'erreur sinon
Cet appel est utile pour gérer les événements lorsqu'ils se produisent ultérieurement après la création du descripteur. Le mécanisme de gestion des événements renvoie le handle et son cookie au gestionnaire d'événements.
Les handles peuvent être attendus pour les événements en utilisant l'appel wait()
.
Attendez()
Attend qu'un événement se produise sur un handle donné pendant une période de temps spécifiée.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: Tout handle renvoyé par l'un des appels d'API
[out] event
: Un pointeur vers la structure représentant un événement qui s'est produit sur ce handle
[in] timeout_msecs
: une valeur de délai d'attente en millisecondes ; une valeur de -1 est un délai d'attente infini
[retval] : NO_ERROR
si un événement valide s'est produit dans un intervalle de temporisation 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 succès ( 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 d' 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 réellement 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 descripteur, comme suit :
- Pour les ports, cette valeur indique qu'il y a une connexion en attente
- Pour les canaux, cette valeur indique qu'une connexion asynchrone (voir
connect()
) a été établie
Les événements suivants ne concernent que les chaînes :
-
IPC_HANDLE_POLL_HUP
- indique qu'un canal a été fermé par un pair -
IPC_HANDLE_POLL_MSG
- indique qu'il y a un message en attente pour ce canal -
IPC_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 plus de détails)
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 collants. Ils persistent tant que la condition sous-jacente persiste (par exemple, tous les messages en attente sont reçus et les demandes de connexion en attente sont traitées). L'exception est le cas de l'événement IPC_HANDLE_POLL_SEND_UNBLOCKED
, qui est effacé lors d'une lecture et l'application n'a qu'une seule chance de le gérer.
Les handles peuvent être détruits en appelant la méthode close()
.
proche()
Détruit la ressource associée au descripteur spécifié et la supprime de la table de descripteurs.
long close(uint32_t handle_id);
[in] handle_id
: Handle à détruire
[retval] : 0 si succès ; une erreur négative sinon
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 dans l'API du serveur
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
: Le nom de chaîne du port (comme décrit ci-dessus). Ce nom doit être unique dans tout le système ; les tentatives de création d'un doublon échoueront.
[in] num_recv_bufs
: Le nombre maximum de buffers 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, donc spécifier 1 ici signifierait 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. Le nombre peut être aussi petit que 1 dans le cas d'un protocole très synchrone (envoyer un message, recevoir une réponse avant d'en envoyer un autre). Mais le nombre peut être supérieur si le client s'attend à envoyer plus d'un message avant qu'une réponse puisse apparaître (par exemple, un message comme prologue et un autre comme commande réelle). Les ensembles de tampons alloués sont par canal, de sorte que deux connexions distinctes (canaux) auraient des ensembles de tampons distincts.
[in] recv_buf_size
: taille maximale de chaque tampon individuel dans 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 les pairs
[in] flags
: Une combinaison de drapeaux qui spécifie un comportement de port supplémentaire
Cette valeur doit être une combinaison des valeurs suivantes :
IPC_PORT_ALLOW_TA_CONNECT
- permet une connexion à partir d'autres applications sécurisées
IPC_PORT_ALLOW_NS_CONNECT
- autorise une connexion depuis le monde non sécurisé
[retval] : Handle vers le port créé si non négatif ou une erreur spécifique si négatif
Le serveur interroge ensuite la liste des descripteurs de port pour les connexions entrantes à l'aide de l'appel wait()
. Lors de la réception d'une demande de connexion indiquée par le bit IPC_HANDLE_POLL_READY
défini dans le champ d' 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 .
J'accepte()
Accepte une connexion entrante et obtient un handle vers un canal.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: Handle représentant le port auquel un client s'est connecté
[out] peer_uuid
: Pointeur vers une structure uuud_t
à remplir avec l'UUID de l'application cliente qui se connecte. Il sera défini sur tous les zéros si la connexion provient du monde non sécurisé
[retval] : Handle vers 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 cliente
Cette section contient les méthodes de l'API client.
Méthodes dans l'API client
relier()
Initie une connexion à un port spécifié par son 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] : handle vers un canal sur lequel des messages peuvent être échangés avec le serveur ; erreur si négatif
Si aucun flags
n'est spécifié (le paramètre flags
est défini sur 0), l'appel de connect()
initie une connexion synchrone à un port spécifié qui renvoie immédiatement une erreur si le port n'existe pas et crée un bloc jusqu'à ce que le serveur accepte autrement 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 à l'exécution, au lieu d'échouer immédiatement.
IPC_CONNECT_ASYNC
- si défini, initie une connexion asynchrone. Une application doit interroger le descripteur renvoyé (en appelant wait()
pour un événement d'achèvement 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 de messagerie
Les appels de l'API de messagerie permettent l'envoi et la lecture de messages via une connexion (canal) précédemment établie. Les appels de l'API de messagerie sont les mêmes pour les serveurs et les clients.
Un client reçoit un handle vers un canal en émettant un appel connect()
, et un serveur obtient un handle de canal à partir d'un appel accept()
, décrit ci-dessus.
Structure d'un message de confiance
Comme le montre ce qui suit, 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 du contenu réel :
/* * 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 plusieurs tampons non contigus représentés par un tableau de structures iovec_t
. Trusty effectue des lectures et des écritures par dispersion sur ces blocs à l'aide du tableau iov
. Le contenu des tampons pouvant être décrits par le tableau iov
est complètement arbitraire.
Méthodes dans l'API de messagerie
envoyer_msg()
Envoie un message sur un canal spécifié.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: Handle du canal sur lequel envoyer le message
[in] msg
: Pointeur vers la ipc_msg_t structure
décrivant le message
[retval] : nombre total d'octets envoyés en cas de succès ; une erreur négative sinon
Si le client (ou le serveur) essaie d'envoyer un message sur le canal et qu'il n'y a pas d'espace dans la file d'attente des messages homologues de destination, le canal peut entrer dans un état d'envoi bloqué (cela ne devrait jamais se produire pour un simple protocole de demande/réponse synchrone mais cela peut arriver dans des cas plus compliqués) qui est indiqué en renvoyant un code d'erreur ERR_NOT_ENOUGH_BUFFER
. Dans un tel 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 retrait, indiqués par le bit IPC_HANDLE_POLL_SEND_UNBLOCKED
défini dans le champ d' event
de la structure uevent_t
renvoyée par l'appel wait()
.
get_msg()
Obtient des méta-informations sur le message suivant dans une file d'attente de messages entrants
d'un canal spécifié.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: Handle du canal sur lequel un nouveau message doit être récupéré
[out] msg_info
: Structure des informations de 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. S'il est configuré et autorisé par le protocole, il peut y avoir plusieurs messages en attente (ouverts) à la fois pour un canal particulier.
[retval] : NO_ERROR
en cas de succès ; une erreur négative sinon
lire_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
: Handle du canal à partir duquel lire le message
[dans] msg_id
: ID du message à lire
[in] offset
: Offset dans le message à partir duquel commencer la lecture
[in] 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 dst
en cas de succès ; une erreur négative sinon
La méthode read_msg
peut être appelée plusieurs fois à partir d'un décalage différent (pas nécessairement séquentiel) selon les besoins.
put_msg()
Supprime un message avec un ID spécifié.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: Handle du canal sur lequel le message est arrivé
[dans] msg_id
: ID du message retiré
[retval] : NO_ERROR
en cas de succès ; une erreur négative sinon
Le contenu du message n'est pas accessible une fois qu'un message a été retiré et que la mémoire tampon qu'il occupait a été libérée.
API de descripteur de fichier
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 séparé de l'espace de descripteur IPC. L'API de descripteur de fichier dans Trusty est similaire à une API traditionnelle basée sur un descripteur de fichier.
Par défaut, il existe 3 descripteurs de fichiers prédéfinis (standard et connus) :
- 0 - entrée standard. L'implémentation par défaut de l'entrée standard
fd
est un no-op (car les applications de confiance ne sont pas censées avoir une console interactive) donc la lecture, l'écriture ou l'invocation deioctl()
surfd
0 devrait 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 LK) vers l'UART et/ou un journal de mémoire disponible du côté non sécurisé, selon la plate-forme et la configuration. Les journaux et les messages de débogage non critiques doivent aller dans la sortie standard. Les méthodes
read()
etioctl()
sont no-ops et doivent renvoyer une erreurERR_NOT_SUPPORTED
. - 2 - erreur type. Les données écrites sur l'erreur standard doivent être acheminées vers l'UART ou le journal de mémoire disponible du côté non sécurisé, selon la plate-forme et la configuration. Il est recommandé d'écrire uniquement les messages critiques sur l'erreur standard, car ce flux est très susceptible d'être non étranglé. Les méthodes
read()
etioctl()
sont no-ops et doivent renvoyer une erreurERR_NOT_SUPPORTED
.
Même si cet ensemble de descripteurs de fichiers peut être étendu pour implémenter plus de fds
(pour implémenter des extensions spécifiques à la plate-forme), l'extension des descripteurs de fichiers doit être exercé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 dans l'API de descripteur de fichier
lis()
Tente de lire jusqu'à count
les 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
[out] buf
: pointeur vers un tampon dans lequel stocker les données
[in] count
: Nombre maximum d'octets à lire
[retval] : nombre d'octets lus renvoyés ; une erreur négative sinon
écrivez()
Écrit pour count
les 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 maximum d'octets à écrire
[retval] : Retourne le nombre d'octets écrits ; une erreur négative sinon
ioctl()
Appelle 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 invoquer ioctl()
[dans] cmd
: La commande ioctl
[in/out] args
: pointeur vers les arguments ioctl()
API diverses
Méthodes dans l'API Divers
obtenir du temps()
Renvoie l'heure système actuelle (en nanosecondes).
long gettime(uint32_t clock_id, uint32_t flags, uint64_t *time);
[in] clock_id
: dépend de la plate-forme ; passer zéro par défaut
[in] flags
: Réservé, doit être égal à zéro
[in] time
: pointeur vers une valeur int64_t
dans laquelle stocker l'heure actuelle
[retval] : NO_ERROR
en cas de succès ; une erreur négative sinon
nanosommeil()
Suspend l'exécution de l'application appelante pendant une période de temps 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 égal à zéro
[in] flags
: Réservé, doit être égal à zéro
[in] sleep_time
: temps de sommeil en nanosecondes
[retval] : NO_ERROR
en cas de succès ; une erreur négative sinon
Exemple de serveur d'applications de confiance
L'exemple d'application suivant montre l'utilisation des API ci-dessus. L'exemple crée un service "echo" 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 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; }
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 mondiales non sécurisées
Un ensemble de services Trusty, publiés 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 s'exécutant du côté non sécurisé.
L'environnement d'exécution du côté non sécurisé (noyau et espace utilisateur) est radicalement différent de l'environnement d'exécution du côté sécurisé. Par conséquent, plutôt qu'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 qui peut être utilisé par les processus de l'espace utilisateur pour communiquer avec les services exécutés du côté sécurisé.
Espace utilisateur Trusty IPC Client API
La bibliothèque Trusty IPC Client API de l'espace utilisateur est une fine couche au-dessus du nœud de périphérique fd
.
Un programme de l'espace utilisateur démarre une session de communication en appelant tipc_connect()
, initialisant une connexion à un service Trusty spécifié. En interne, l'appel tipc_connect()
ouvre un nœud de périphérique spécifié pour obtenir un descripteur de fichier et invoque un 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 résultant 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 requise.
Le descripteur de fichier obtenu par l'appel tipc_connect()
se comporte comme un nœud de périphérique caractère typique ; le descripteur de fichier :
- Peut être basculé en mode non bloquant si nécessaire
- Peut être écrit à l'aide d'un appel standard
write()
pour envoyer des messages à l'autre côté - Peut être interrogé (à l'aide d'appels
poll()
ouselect()
) pour la disponibilité des messages entrants en tant que descripteur de fichier normal - 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 le fd
spécifié. Toutes les données transmises à l'appel write()
ci-dessus sont transformées en un message par le pilote trusty-ipc. Le message est transmis au côté sécurisé où les données sont gérées par le sous-système IPC dans le noyau Trusty et acheminées vers la destination appropriée et transmises à une boucle d'événements d'application en tant IPC_HANDLE_POLL_MSG
sur un descripteur de canal particulier. Selon le protocole spécifique au service, le service Trusty peut envoyer un ou plusieurs messages de réponse qui sont renvoyés au côté non sécurisé et placés dans la file d'attente de messages de descripteur de fichier de canal appropriée pour être récupérés par l'application de l'espace utilisateur read()
appel.
tipc_connect()
Ouvre un nœud de périphérique tipc
spécifié et initie 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 Trusty IPC à ouvrir
[in] srv_name
: Nom d'un service Trusty publié auquel se connecter
[retval] : Descripteur de fichier valide en cas de succès, -1 sinon.
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 Trusty IPC du noyau est disponible pour les pilotes du noyau. L'API Trusty IPC de l'espace utilisateur est implémentée au-dessus de cette API.
En général, l'utilisation typique de cette API consiste en un appelant créant un objet struct tipc_chan
à l'aide de la fonction tipc_create_channel()
, puis en utilisant l'appel tipc_chan_connect()
pour initier une connexion au service Trusty IPC exécuté du 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.
Lors de la réception d'une notification (via le rappel handle_event()
) indiquant qu'une connexion a été établie avec succès, un appelant effectue les actions suivantes :
- Obtient un tampon de messages à l'aide de l'appel
tipc_chan_get_txbuf_timeout()
- Compose un message, et
- Met le message en file d'attente à l'aide de la méthode
tipc_chan_queue_msg()
pour livraison à un service Trusty (du 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 messages car le tampon de messages retourne finalement dans le pool de tampons libres après traitement par le côté distant (pour une réutilisation ultérieure, pour d'autres messages). L'utilisateur n'a besoin d'appeler tipc_chan_put_txbuf()
que s'il ne parvient pas à mettre ce tampon en file d'attente ou s'il n'est plus nécessaire.
Un utilisateur de l'API reçoit des messages du côté distant en gérant un rappel de notification handle_msg()
(qui est appelé dans le contexte de la file d'attente de travail trusty-ipc rx
) qui fournit un pointeur vers un tampon rx
contenant un message entrant à gérer.
On s'attend à ce que l'implémentation du rappel handle_msg()
renvoie un pointeur vers une structure valide struct tipc_msg_buf
. Il peut être identique au tampon des messages entrants s'il est géré localement et n'est plus nécessaire. Alternativement, il peut 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
détaché 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 dans l'API Kernel Trusty IPC Client
tipc_create_channel()
Crée et configure une instance d'un canal Trusty IPC pour un périphérique 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 périphérique est créé
[in] ops
: pointeur vers une struct tipc_chan_ops
, avec des rappels spécifiques à l'appelant remplis
[in] cb_arg
: pointeur vers les données qui seront transmises aux rappels tipc_chan_ops
[retval] : pointeur vers une instance nouvellement créée de struct tipc_chan
en cas de succès, ERR_PTR(err)
sinon
En général, un appelant doit fournir deux rappels qui sont invoqués de manière asynchrone lorsque l'activité correspondante se produit.
L' void (*handle_event)(void *cb_arg, int event)
est invoqué 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
: un événement qui peut prendre l'une des valeurs suivantes :
-
TIPC_CHANNEL_CONNECTED
- indique une connexion réussie au côté distant -
TIPC_CHANNEL_DISCONNECTED
- indique que le côté distant a refusé la nouvelle demande de connexion ou a demandé la déconnexion pour le canal précédemment connecté -
TIPC_CHANNEL_SHUTDOWN
- indique que le côté distant est en train de s'arrêter, mettant définitivement fin à toutes les connexions
Le struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
est invoqué pour notifier 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 unestruct tipc_msg_buf
décrivant un message entrant - [retval] : l'implémentation de rappel est censée renvoyer un pointeur vers une
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 requis (ou il peut s'agir d'un nouveau tampon obtenu par le appeltipc_chan_get_rxbuf()
)
tipc_chan_connect()
Initie une connexion au service Trusty IPC spécifié.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: Pointeur vers un canal renvoyé 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 succès, une erreur négative sinon
L'appelant est averti lorsqu'une connexion est établie en recevant un rappel handle_event
.
tipc_chan_shutdown()
Termine une connexion au service Trusty IPC 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 retourné par un appel tipc_create_chan()
tipc_chan_destroy()
Détruit un canal Trusty IPC 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 messages qui peut ê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 spécifié (en millisecondes).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: pointeur vers le canal dans lequel mettre un message en file d'attente
[in] chan
: Délai maximal d'attente jusqu'à ce que le tampon tx
devienne disponible
[retval] : un tampon de message valide en cas de succès, ERR_PTR(err)
en cas d'erreur
tipc_chan_queue_msg()
Met en file d'attente un message à envoyer sur les canaux Trusty IPC spécifiés.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: pointeur vers le canal dans lequel 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 succès, une erreur négative sinon
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 message
[in] mb
: Pointeur vers le tampon de messages à libérer
[retval] : aucun
tipc_chan_get_rxbuf()
Obtient un nouveau tampon de messages qui peut être utilisé pour recevoir des messages sur 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 message
[retval] : un tampon de message valide en cas de succès, ERR_PTR(err)
en cas d'erreur
tipc_chan_put_rxbuf()
Libère un tampon de messages 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 message
[in] mb
: Pointeur vers un tampon de messages à libérer
[retval] : aucun