Riferimento API Trusty

Trusty fornisce API per lo sviluppo di due classi di app e servizi:

  • App e servizi attendibili che vengono eseguiti sul processore TEE
  • App normali e non attendibili che vengono eseguite sul processore principale e utilizzano i servizi forniti dalle app attendibili

L'API Trusty descrive in generale il sistema di comunicazione interprocessuale (IPC) di Trusty, incluse le comunicazioni con il mondo non sicuro. Il software in esecuzione sul processore principale può utilizzare le API attendibili per connettersi ad app e servizi attendibili e scambiare messaggi arbitrari con loro, proprio come un servizio di rete su IP. Spetta all'app determinare il formato e la semantica dei dati di questi messaggi utilizzando un protocollo a livello di app. L'invio affidabile dei messaggi è garantito dall'infrastruttura Trusty sottostante (sotto forma di driver in esecuzione sul processore principale) e la comunicazione è completamente asincrona.

Porte e canali

Le porte vengono utilizzate dalle app attendibili per esporre gli endpoint di servizio sotto forma di un percorso denominato a cui si connettono i client. In questo modo, i client possono utilizzare un ID servizio semplice basato su stringhe. La convenzione di denominazione è in stile DNS inverso, ad es. com.google.servicename.

Quando un client si connette a una porta, riceve un canale per interagire con un servizio. Il servizio deve accettare una connessione in arrivo e, quando lo fa, riceve anche un canale. In sostanza, le porte vengono utilizzate per cercare i servizi e la comunicazione avviene tramite una coppia di canali collegati (ovvero istanze di connessione su una porta). Quando un client si connette a una porta, viene stabilita una connessione simmetrica e bidirezionale. Utilizzando questo percorso full-duplex, i client e i server possono scambiarsi messaggi arbitrari finché una delle due parti non decide di disconnettere la connessione.

Solo le app attendibili lato sicuro o i moduli del kernel attendibili possono creare porte. Le app in esecuzione sul lato non sicuro (nel mondo normale) possono collegarsi solo ai servizi pubblicati dal lato sicuro.

A seconda dei requisiti, un'app attendibile può essere contemporaneamente un client e un server. Un'app attendibile che pubblica un servizio (come server) potrebbe dover connettersi ad altri servizi (come client).

Gestisci API

Gli handle sono numeri interi non firmati che rappresentano risorse come porte e canali, in modo simile ai descrittori file in UNIX. Una volta creati, gli handle vengono inseriti in una tabella di handle specifica per l'app e possono essere richiamati in un secondo momento.

Un chiamante può associare dati privati a un handle utilizzando il metodo set_cookie().

Metodi nell'API Handle

Gli handle sono validi solo nel contesto di un'app. Un'app non deve trasmettere il valore di un handle ad altre app, a meno che non sia specificato esplicitamente. Un valore dell'handle deve essere interpretato solo confrontandolo con il valore INVALID_IPC_HANDLE #define, che un'app può utilizzare come indicazione che un handle non è valido o non è impostato.

Associa i dati privati forniti dall'utente chiamante a un handle specificato.

long set_cookie(uint32_t handle, void *cookie)

[in] handle: qualsiasi handle restituito da una delle chiamate API

[in] cookie: puntatore a dati arbitrari nello spazio utente dell'app Trusty

[retval]: NO_ERROR in caso di esito positivo, < 0 codice di errore in caso contrario

Questa chiamata è utile per gestire gli eventi quando si verificano in un secondo momento dopo la creazione dell'handle. Il meccanismo di gestione degli eventi fornisce nuovamente l'handle e il relativo cookie al gestore di eventi.

È possibile attendere gli eventi per i handle utilizzando la chiamata wait().

wait()

Attende che si verifichi un evento su un determinato handle per un periodo di tempo specificato.

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

[in] handle_id: qualsiasi handle restituito da una delle chiamate API

[out] event: un puntatore alla struttura che rappresenta un evento che si è verificato su questo handle

[in] timeout_msecs: un valore di timeout in millisecondi; un valore di -1 indica un timeout infinito

[retval]: NO_ERROR se si è verificato un evento valido entro un intervallo di timeout specificato; ERR_TIMED_OUT se è trascorso un timeout specificato, ma non si è verificato alcun evento; < 0 per altri errori

In caso di esito positivo (retval == NO_ERROR), la chiamata wait() compila una struttura uevent_t specificata con informazioni sull'evento che si è verificato.

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;

Il campo event contiene una combinazione dei seguenti valori:

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: non ci sono eventi in attesa, l'utente che chiama deve riavviare l'attesa

IPC_HANDLE_POLL_ERROR - si è verificato un errore interno non specificato

IPC_HANDLE_POLL_READY: dipende dal tipo di handle, come segue:

  • Per le porte, questo valore indica che è presente una connessione in attesa
  • Per i canali, questo valore indica che è stata stabilita una connessione asincrona (vedi connect()).

I seguenti eventi sono pertinenti solo per i canali:

  • IPC_HANDLE_POLL_HUP: indica che un canale è stato chiuso da un pari
  • IPC_HANDLE_POLL_MSG: indica che è presente un messaggio in attesa per questo canale
  • IPC_HANDLE_POLL_SEND_UNBLOCKED: indica che un chiamante che in precedenza è stato bloccato per l'invio potrebbe tentare di inviare di nuovo un messaggio (per maggiori dettagli, leggi la descrizione di send_msg())

Un gestore eventi deve essere preparato a gestire una combinazione di eventi specificati, poiché è possibile impostare più bit contemporaneamente. Ad esempio, per un canale è possibile avere messaggi in attesa e una connessione chiusa da un peer contemporaneamente.

La maggior parte degli eventi è fissa. Persistono finché persiste la condizione di base (ad esempio, tutti i messaggi in attesa vengono ricevuti e le richieste di connessione in attesa vengono gestite). L'eccezione è il caso dell'evento IPC_HANDLE_POLL_SEND_UNBLOCKED, che viene cancellato al termine di una lettura e l'app ha una sola possibilità di gestirlo.

Gli handle possono essere distrutti chiamando il metodo close().

close()

Elimina la risorsa associata all'handle specificato e la rimuove dalla tabella degli handle.

long close(uint32_t handle_id);

[in] handle_id: handle da eliminare

[retval]: 0 se l'operazione è riuscita; un errore negativo in caso contrario

API server

Un server inizia creando una o più porte con nome che rappresentano i suoi endpoint di servizio. Ogni porta è rappresentata da un handle.

Metodi nell'API Server

port_create()

Crea una porta di servizio denominata.

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

[in] path: il nome della stringa della porta (come descritto sopra). Questo nome deve essere univoco nel sistema; i tentativi di creare un duplicato non andranno a buon fine.

[in] num_recv_bufs: il numero massimo di buffer che un canale su questa porta può preallocare per facilitare lo scambio di dati con il client. I buffer vengono conteggiati distintamente per i dati in entrambe le direzioni, pertanto se specifichi 1 qui, significa che 1 buffer di invio e 1 buffer di ricezione sono preallocati. In generale, il numero di buffer obbligatori dipende dall'accordo di protocollo di livello superiore tra il client e il server. Il numero può essere anche 1 in caso di un protocollo molto sincrono (invia messaggio, ricevi risposta prima di inviarne un altro). Tuttavia, il numero può essere superiore se il client si aspetta di inviare più di un messaggio prima che possa essere visualizzata una risposta (ad es.un messaggio come prologo e un altro come comando effettivo). I set di buffer allocati sono per canale, quindi due connessioni (canali) separate avranno set di buffer distinti.

[in] recv_buf_size: dimensione massima di ogni singolo buffer nell'insieme di buffer sopra indicato. Questo valore dipende dal protocollo e limita in modo efficace le dimensioni massime dei messaggi che puoi scambiare con il peer

[in] flags: una combinazione di flag che specifica un comportamento aggiuntivo della porta

Questo valore deve essere una combinazione dei seguenti valori:

IPC_PORT_ALLOW_TA_CONNECT: consente una connessione da altre app sicure

IPC_PORT_ALLOW_NS_CONNECT: consente una connessione dal mondo non sicuro

[retval]: handle della porta creata se non negativo o un errore specifico se negativo

Il server esegue quindi la polling dell'elenco degli handle delle porte per le connessioni in arrivo utilizzando la chiamata wait(). Al ricevimento di una richiesta di connessione indicata dal bit IPC_HANDLE_POLL_READY impostato nel campo event della struttura uevent_t, il server deve chiamare accept() per completare l'instaurazione di una connessione e creare un canale (rappresentato da un altro handle) che può essere sottoposto a polling per i messaggi in arrivo.

accept()

Accetta una connessione in arrivo e recupera l'handle di un canale.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id: handle che rappresenta la porta a cui si è connesso un client

[out] peer_uuid: puntatore a una struttura uuid_t da compilare con l'UUID dell'app client in connessione. È impostato su tutti zeri se la connessione ha avuto origine dal mondo non sicuro

[retval]: handle di un canale (se non negativo) su cui il server può scambiare messaggi con il client (o un codice di errore in caso contrario)

API client

Questa sezione contiene i metodi dell'API client.

Metodi nell'API client

connect()

Avvia una connessione a una porta specificata per nome.

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

[in] path: il nome di una porta pubblicata da un'app attendibile

[in] flags: specifica un comportamento aggiuntivo facoltativo

[retval]: handle di un canale tramite il quale è possibile scambiare messaggi con il server; errore se negativo

Se non vengono specificati flags (il parametro flags è impostato su 0), la chiamata a connect() avvia una connessione sincrona a una porta specificata che restituisce immediatamente un errore se la porta non esiste e crea un blocco finché il server non accetta una connessione.

Questo comportamento può essere modificato specificando una combinazione di due valori, come descritto di seguito:

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

IPC_CONNECT_WAIT_FOR_PORT: forza una chiamata connect() a attendere se la porta specificata non esiste immediatamente al momento dell'esecuzione, invece di fallire immediatamente.

IPC_CONNECT_ASYNC: se impostato, avvia una connessione asincrona. Un'app deve eseguire il polling per il handle restituito chiamando wait() per un evento di completamento della connessione indicato dal bit IPC_HANDLE_POLL_READY impostato nel campo evento della struttura uevent_t prima di iniziare il normale funzionamento.

API Messaging

Le chiamate all'API Messaging consentono l'invio e la lettura di messaggi tramite una connessione (canale) stabilita in precedenza. Le chiamate all'API Messaging sono uguali per i server e i client.

Un client riceve un handle di un canale emettendo una chiamata connect() e un server riceve un handle di canale da una chiamata accept(), come descritto sopra.

Struttura di un messaggio attendibile

Come mostrato di seguito, i messaggi scambiati dall'API Trusty hanno una struttura minima, lasciando al server e al client il compito di concordare la semantica dei contenuti effettivi:

/*
 *  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 messaggio può essere composto da uno o più buffer non contigui rappresentati da un array di strutture iovec_t. Trusty esegue letture e scritture su questi blocchi con l'uso dell'array iov. I contenuti dei buffer che possono essere descritti dall'array iov sono completamente arbitrari.

Metodi nell'API Messaging

send_msg()

Invia un messaggio su un canale specificato.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle: handle del canale su cui inviare il messaggio

[in] msg: puntatore all'ipc_msg_t structure che descrive il messaggio

[retval]: numero totale di byte inviati in caso di esito positivo; un errore negativo in caso contrario

Se il client (o il server) sta tentando di inviare un messaggio tramite il canale e non c'è spazio nella coda dei messaggi del peer di destinazione, il canale potrebbe entrare in uno stato di invio bloccato (questo non dovrebbe mai accadere per un semplice protocollo di richiesta/risposta sincrono, ma potrebbe verificarsi in casi più complicati), che viene indicato restituendo un codice di errore ERR_NOT_ENOUGH_BUFFER. In questo caso, il chiamante deve attendere che il peer liberi un po' di spazio nella coda di ricezione recuperando i messaggi di gestione e ritiro, indicati dal bit IPC_HANDLE_POLL_SEND_UNBLOCKED impostato nel campo event della struttura uevent_t restituita dalla chiamata wait().

get_msg()

Recupera le metainformazioni sul messaggio successivo in una coda di messaggi in arrivo

di un canale specificato.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle: handle del canale su cui deve essere recuperato un nuovo messaggio

[out] msg_info: struttura delle informazioni del messaggio descritta come segue:

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

A ogni messaggio viene assegnato un ID univoco nell'insieme di messaggi in sospeso e viene compilata la lunghezza totale di ogni messaggio. Se configurato e consentito dal protocollo, possono essere presenti più messaggi in attesa (aperti) contemporaneamente per un determinato canale.

[retval]: NO_ERROR in caso di esito positivo; un errore negativo in caso contrario

read_msg()

Legge i contenuti del messaggio con l'ID specificato a partire dall'offset specificato.

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

[in] handle: handle del canale da cui leggere il messaggio

[in] msg_id: l'ID del messaggio da leggere

[in] offset: l'offset nel messaggio da cui iniziare a leggere

[out] msg: puntatore alla struttura ipc_msg_t che descrive un insieme di buffer in cui memorizzare i dati dei messaggi in arrivo

[retval]: numero totale di byte memorizzati nei buffer msg in caso di esito positivo; un errore negativo in caso contrario

Il metodo read_msg può essere chiamato più volte a partire da un offset diverso (non necessariamente sequenziale), in base alle esigenze.

put_msg()

Ritira un messaggio con un ID specificato.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle: handle del canale su cui è arrivato il messaggio

[in] msg_id: ID del messaggio che verrà ritirato

[retval]: NO_ERROR in caso di esito positivo; un errore negativo in caso contrario

Non è possibile accedere ai contenuti di un messaggio dopo che è stato ritirato e il buffer che occupava è stato liberato.

API File Descriptor

L'API File Descriptor include le chiamate read(), write() e ioctl(). Tutte queste chiamate possono operare su un insieme predefinito (statico) di descrittori di file tradizionalmente rappresentati da piccoli numeri. Nell'implementazione attuale, lo spazio dei descrittori file è separato dallo spazio degli handle IPC. L'API File Descriptor in Trusty è simile a un'API basata su descrittori file tradizionale.

Per impostazione predefinita, sono disponibili tre descrittori di file predefiniti (standard e well-known):

  • 0: input standard. L'implementazione predefinita dell'input standard fd è un'operazione no-op (poiché non è previsto che le app attendibili abbiano una console interattiva), pertanto la lettura, la scrittura o l'invocazione di ioctl() su fd 0 dovrebbe restituire un errore ERR_NOT_SUPPORTED.
  • 1 - output standard. I dati scritti nell'output standard possono essere instradati (a seconda del livello di debug di LK) a UART e/o a un log della memoria disponibile sul lato non sicuro, a seconda della piattaforma e della configurazione. I log e i messaggi di debug non critici devono essere visualizzati nell'output standard. I metodi read() e ioctl() non eseguono alcuna operazione e dovrebbero restituire un errore ERR_NOT_SUPPORTED.
  • 2 - errore standard. I dati scritti nell'errore standard devono essere inoltrati al log UART o della memoria disponibile sul lato non sicuro, a seconda della piattaforma e della configurazione. Ti consigliamo di scrivere solo messaggi critici in standard error, poiché è molto probabile che questo stream non sia limitato. I metodi read() e ioctl() sono no-op e dovrebbero restituire un errore ERR_NOT_SUPPORTED.

Anche se questo insieme di descrittori file può essere esteso per implementare più fds (per implementare estensioni specifiche della piattaforma), l'estensione dei descrittori file deve essere eseguita con cautela. L'estensione dei descrittori dei file è soggetta a creare conflitti e in genere non è consigliata.

Metodi nell'API File Descriptor

read()

Tenta di leggere fino a count byte di dati da un descrittore file specificato.

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

[in] fd: descrittore del file da cui leggere

[out] buf: puntatore a un buffer in cui memorizzare i dati

[in] count: numero massimo di byte da leggere

[retval]: numero di byte letti restituito; in caso contrario, un errore negativo

write()

Scrive fino a count byte di dati nel descrittore file specificato.

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

[in] fd: descrittore del file in cui scrivere

[out] buf: puntatore ai dati da scrivere

[in] count: numero massimo di byte da scrivere

[retval]: numero di byte scritti restituito; in caso contrario, un errore negativo

ioctl()

Richiama un comando ioctl specificato per un determinato descrittore file.

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

[in] fd: descrittore del file su cui chiamare ioctl()

[in] cmd: il comando ioctl

[in/out] args: puntatore agli argomenti ioctl()

API varie

Metodi nell'API Varie

gettime()

Restituisce l'ora di sistema corrente (in nanosecondi).

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

[in] clock_id: dipende dalla piattaforma; passa zero per il valore predefinito

[in] flags: riservato, deve essere zero

[out] time: puntatore a un valore int64_t in cui memorizzare l'ora corrente

[retval]: NO_ERROR in caso di esito positivo; un errore negativo in caso contrario

nanosleep()

Sospende l'esecuzione dell'app chiamante per un determinato periodo di tempo e la riprende al termine di questo periodo.

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

[in] clock_id: riservato, deve essere zero

[in] flags: riservato, deve essere zero

[in] sleep_time: tempo di sospensione in nanosecondi

[retval]: NO_ERROR in caso di esito positivo; un errore negativo in caso contrario

Esempio di server di app attendibile

La seguente app di esempio mostra l'utilizzo delle API precedenti. L'esempio crea un servizio "echo" che gestisce più connessioni in entrata e ritrasmette al chiamante tutti i messaggi ricevuti dai client provenienti dal lato sicuro o non sicuro.

#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;
}

Il metodo run_end_to_end_msg_test() invia 10.000 messaggi in modo asincrono al servizio "echo" e gestisce le risposte.

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 e app non sicure

Un insieme di servizi attendibili, pubblicati dal lato sicuro e contrassegnati con l'attributo IPC_PORT_ALLOW_NS_CONNECT, sono accessibili ai programmi del kernel e dello spazio utente in esecuzione sul lato non sicuro.

L'ambiente di esecuzione lato non sicuro (kernel e spazio utente) è molto diverso dall'ambiente di esecuzione lato sicuro. Pertanto, anziché una singola libreria per entrambi gli ambienti, esistono due diversi insiemi di API. Nel kernel, l'API client è fornita dal driver del kernel trusty-ipc e registra un nodo del dispositivo di caratteri che può essere utilizzato dalle procedure nello spazio utente per comunicare con i servizi in esecuzione sul lato sicuro.

API client IPC attendibile nello spazio utente

La libreria dell'API client IPC Trusty nello spazio utente è un livello sottile sopra il node del dispositivo fd.

Un programma nello spazio utente avvia una sessione di comunicazione chiamando tipc_connect() e inizializzando una connessione a un servizio attendibile specificato. Internamente, la chiamata tipc_connect() apre un nodo del dispositivo specificato per ottenere un descrittore file e richiama una chiamata TIPC_IOC_CONNECT ioctl() con il parametro argp che punta a una stringa contenente un nome di servizio a cui connettersi.

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

Il descrittore file risultante può essere utilizzato solo per comunicare con il servizio per cui è stato creato. Il descrittore file deve essere chiuso chiamando tipc_close() quando la connessione non è più necessaria.

Il descrittore di file ottenuto dalla chiamata tipc_connect() si comporta come un tipico nodo del dispositivo di caratteri. Il descrittore di file:

  • Se necessario, è possibile passare alla modalità non bloccante
  • Possono essere scritti utilizzando una chiamata write() standard per inviare messaggi all'altra parte
  • Può essere sottoposto a polling (utilizzando chiamate poll() o select()) per verificare la disponibilità dei messaggi in arrivo come descrittore file normale
  • Può essere letto per recuperare i messaggi in arrivo

Un chiamante invia un messaggio al servizio attendibile eseguendo una chiamata di scrittura per il fd specificato. Tutti i dati passati alla chiamata write() riportata sopra vengono trasformati in un messaggio dal driver trusty-ipc. Il messaggio viene inviato al lato sicuro, dove i dati vengono gestiti dal sottosistema IPC nel kernel Trusty, inoltrati alla destinazione corretta e inviati a un loop di eventi dell'app come evento IPC_HANDLE_POLL_MSG su un handle del canale specifico. A seconda del protocollo specifico del servizio, il servizio attendibile può inviare uno o più messaggi di risposta che vengono inviati di nuovo al lato non sicuro e inseriti nella coda dei messaggi del descrittore file del canale appropriato per essere recuperati dalla chiamata read() dell'app dello spazio utente.

tipc_connect()

Apre un nodo del dispositivo tipc specificato e avvia una connessione a un servizio attendibile specificato.

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

[in] dev_name: percorso del nodo del dispositivo IPC attendibile da aprire

[in] srv_name: nome di un servizio attendibile pubblicato a cui connettersi

[retval]: descrittore file valido in caso di esito positivo, -1 in caso contrario.

tipc_close()

Chiude la connessione al servizio attendibile specificato da un descrittore file.

int tipc_close(int fd);

[in] fd: descrittore del file aperto in precedenza da una chiamata tipc_connect()

Kernel Trusty IPC Client API

L'API client IPC Trusty del kernel è disponibile per i driver del kernel. L'API Trusty IPC per lo spazio utente è implementata su questa API.

In generale, l'utilizzo tipico di questa API consiste nel creare un oggetto struct tipc_chan utilizzando la funzione tipc_create_channel() e poi utilizzare la chiamata tipc_chan_connect() per avviare una connessione al servizio Trusty IPC in esecuzione sul lato sicuro. La connessione al lato remoto può essere interrotta chiamando tipc_chan_shutdown() seguita da tipc_chan_destroy() per ripulire le risorse.

Quando un chiamante riceve una notifica (tramite il handle_event() callback) che indica che una connessione è stata stabilita correttamente, esegue quanto segue:

  • Ottiene un buffer di messaggi utilizzando la chiamata tipc_chan_get_txbuf_timeout()
  • Componi un messaggio e
  • Mette in coda il messaggio utilizzando il metodo tipc_chan_queue_msg() per la consegna a un servizio attendibile (lato sicuro), a cui è collegato il canale

Una volta completata la coda, chi chiama non deve più tenere conto del buffer dei messaggi perché, al termine dell'elaborazione da parte del lato remoto, il buffer dei messaggi torna al pool di buffer liberi (per essere riutilizzato in un secondo momento per altri messaggi). L'utente deve chiamare tipc_chan_put_txbuf() solo se non riesce a mettere in coda questo buffer o se non è più necessario.

Un utente dell'API riceve i messaggi dal lato remoto gestendo un callback di notifica handle_msg() (chiamato nel contesto della coda di lavoro rx trusty-ipc) che fornisce un puntatore a un buffer rx contenente un messaggio in arrivo da gestire.

È previsto che l'implementazione del callback handle_msg() restituisca un puntatore a un struct tipc_msg_buf valido. Può essere uguale al buffer dei messaggi in arrivo se viene gestito localmente e non è più necessario. In alternativa, può essere un nuovo buffer ottenuto da una chiamata tipc_chan_get_rxbuf() se il buffer in arrivo è in coda per ulteriore elaborazione. Un buffer rx scollegato deve essere monitorato e eventualmente rilasciato utilizzando una chiamata tipc_chan_put_rxbuf() quando non è più necessario.

Metodi nell'API client IPC Kernel Trusty

tipc_create_channel()

Crea e configura un'istanza di un canale IPC attendibile per un determinato dispositivo trusty-ipc.

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

[in] dev: puntatore al canale trusty-ipc per il quale viene creato il canale del dispositivo

[in] ops: puntatore a un struct tipc_chan_ops con i richiamati specifici del chiamante compilati

[in] cb_arg: puntatore ai dati passati ai callback tipc_chan_ops

[retval]: puntatore a un'istanza appena creata di struct tipc_chan in caso di esito positivo, ERR_PTR(err) in caso contrario

In generale, un chiamante deve fornire due callback invocati in modo asincrono quando si verifica l'attività corrispondente.

L'evento void (*handle_event)(void *cb_arg, int event)viene invocato per informare un chiamante di una modifica dello stato del canale.

[in] cb_arg: puntatore ai dati passati a una chiamatatipc_create_channel()

[in] event: un evento che può essere uno dei seguenti valori:

  • TIPC_CHANNEL_CONNECTED: indica una connessione riuscita al lato remoto
  • TIPC_CHANNEL_DISCONNECTED: indica che il lato remoto ha rifiutato la nuova richiesta di connessione o ha richiesto la disconnessione per il canale collegato in precedenza
  • TIPC_CHANNEL_SHUTDOWN: indica che il lato remoto si sta spegnendo, terminando definitivamente tutte le connessioni

Il struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) callback viene invocato per fornire una notifica che indica che è stato ricevuto un nuovo messaggio su un canale specificato:

  • [in] cb_arg: puntatore ai dati passati alla chiamata tipc_create_channel()
  • [in] mb: puntatore a un struct tipc_msg_buf che descrive un messaggio in arrivo
  • [retval]: l'implementazione del callback dovrebbe restituire un puntatore a un struct tipc_msg_buf che può essere lo stesso puntatore ricevuto come parametro mb se il messaggio viene gestito localmente e non è più richiesto (o può essere un nuovo buffer ottenuto dalla chiamata tipc_chan_get_rxbuf())

tipc_chan_connect()

Avvia una connessione al servizio Trusty IPC specificato.

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

[in] chan: puntatore a un canale restituito dalla chiamatatipc_create_chan()

[in] port: puntatore a una stringa contenente il nome del servizio a cui connettersi

[retval]: 0 in caso di esito positivo, un errore negativo in caso contrario

L'utente che chiama viene avvisato quando viene stabilita una connessione ricevendo un callbackhandle_event.

tipc_chan_shutdown()

Termina una connessione al servizio Trusty IPC avviata precedentemente da una chiamata tipc_chan_connect().

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan: puntatore a un canale restituito da una chiamata tipc_create_chan()

tipc_chan_destroy()

Distruisce un canale IPC attendibile specificato.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan: puntatore a un canale restituito dalla chiamatatipc_create_chan()

tipc_chan_get_txbuf_timeout()

Ottiene un buffer di messaggi che può essere utilizzato per inviare dati su un canale specificato. Se il buffer non è immediatamente disponibile, il chiamante potrebbe essere bloccato per il timeout specificato (in millisecondi).

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

[in] chan: puntatore al canale a cui mettere in coda un messaggio

[in] chan: il timeout massimo da attendere fino a quando il buffer tx non diventa disponibile

[retval]: un buffer di messaggi valido in caso di esito positivo,ERR_PTR(err) in caso di errore

tipc_chan_queue_msg()

Mette in coda un messaggio da inviare tramite i canali IPC attendibili specificati.

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

[in] chan: puntatore al canale in cui mettere in coda il messaggio

[in] mb: Puntatore al messaggio da mettere in coda (ottenuto da una chiamata tipc_chan_get_txbuf_timeout())

[retval]: 0 in caso di esito positivo, un errore negativo in caso contrario

tipc_chan_put_txbuf()

Libera il buffer dei messaggi Tx specificato precedentemente ottenuto da una chiamata tipc_chan_get_txbuf_timeout().

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

[in] chan: puntatore al canale a cui appartiene questo buffer di messaggi

[in] mb: puntatore al buffer dei messaggi da rilasciare

[retval]: None

tipc_chan_get_rxbuf()

Recupera un nuovo buffer di messaggi che può essere utilizzato per ricevere messaggi tramite il canale specificato.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan: puntatore a un canale a cui appartiene questo buffer di messaggi

[retval]: un buffer di messaggi valido in caso di esito positivo, ERR_PTR(err) in caso di errore

tipc_chan_put_rxbuf()

Libera un buffer di messaggi specificato ottenuto in precedenza da una chiamatatipc_chan_get_rxbuf().

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

[in] chan: puntatore a un canale a cui appartiene questo buffer di messaggi

[in] mb: puntatore a un buffer di messaggi da rilasciare

[retval]: None