Riferimento API Trusty

Trusty fornisce API per lo sviluppo di due classi di applicazioni/servizi:

  • Applicazioni o servizi attendibili in esecuzione sul processore TEE
  • Applicazioni normali/non attendibili che vengono eseguite sul processore principale e utilizzano i servizi forniti da applicazioni attendibili

Il Trusty l'API descrive generalmente il sistema di comunicazione tra processi (IPC) Trusty, comprese le comunicazioni con il mondo non sicuro. il software in esecuzione Il processore principale può usare le API Trusty per connettersi ad applicazioni/servizi attendibili e scambiare messaggi arbitrari con loro, proprio come con un servizio di rete su IP. Spetta all'applicazione determinare il formato e la semantica dei dati utilizzando un protocollo a livello di app. La consegna 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 applicazioni Trusty per esporre gli endpoint dei servizi nel modulo di un percorso denominato a cui si connettono i client. Questo fornisce una semplice query basata su stringhe l'ID servizio che i client possono utilizzare. La convenzione di denominazione è di tipo DNS inverso nome, ad esempio com.google.servicename.

Quando un client si connette a una porta, riceve un canale per l'interazione. con un servizio. Il servizio deve accettare una connessione in entrata e quando riceve un canale. In sostanza, le porte sono utilizzate per cercare servizi e la comunicazione avviene su una coppia di canali collegati (ad es. di connessione su una porta). Quando un client si connette a una porta, viene utilizzato uno stile simmetrico, viene stabilita una connessione bidirezionale. Utilizzando questo percorso full-duplex, i client e i server possono scambiare messaggi arbitrari fino a quando una delle parti non decide di interrompere la connessione.

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

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

API Handle

Gli handle sono numeri interi senza segno che rappresentano risorse come porte e simili ai descrittori dei file in UNIX. Una volta creati, gli handle vengono posizionati in una tabella di handle specifica per l'applicazione e vi si può fare riferimento 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'applicazione. Un'applicazione dovrebbe non passare il valore di un handle ad altre applicazioni a meno che non venga specificato. Un valore handle deve essere interpretato solo confrontandolo con INVALID_IPC_HANDLE #define, che un'applicazione può utilizzare come che indica che un handle non è valido o non è impostato.

Associa i dati privati forniti dal 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: punta ai dati arbitrari dello spazio utente nell'applicazione Trusty

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

Questa chiamata è utile per gestire gli eventi che si verificano in un secondo momento dopo è stato creato l'handle. Il meccanismo di gestione degli eventi fornisce l'handle e il relativo cookie al gestore di eventi.

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

wait()

Attende che si verifichi un evento su un determinato handle per il 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 il valore -1 è un timeout infinito

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

Se l'operazione riesce (retval == NO_ERROR), la chiamata a wait() compila una struttura uevent_t specificata con informazioni l'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 - nessun evento è effettivamente in attesa, il chiamante deve riavviare l'attesa

IPC_HANDLE_POLL_ERROR. Si è verificato un errore interno non specificato

IPC_HANDLE_POLL_READY dipende dal tipo di handle:

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

I seguenti eventi sono pertinenti solo per i canali:

  • IPC_HANDLE_POLL_HUP. Indica che un canale è stato chiuso da un peer.
  • IPC_HANDLE_POLL_MSG. Indica che è presente un messaggio in attesa per questo canale.
  • IPC_HANDLE_POLL_SEND_UNBLOCKED. Indica che in precedenza il chiamante con blocco di invio può tentare di inviare un messaggio di nuovo (vedi la descrizione di send_msg() per i dettagli)

Un gestore di eventi deve essere pronto a gestire una combinazione perché è possibile impostare più bit contemporaneamente. Ad esempio, per un di destinazione, è possibile che ci siano messaggi in sospeso e che la connessione sia chiusa peer contemporaneamente.

La maggior parte degli eventi è fisso. Permangono fintanto che la condizione sottostante persiste (ad esempio, tutti i messaggi in sospeso vengono ricevuti e la connessione in attesa vengono gestite). Fa eccezione il caso l'evento IPC_HANDLE_POLL_SEND_UNBLOCKED, che viene autorizzato al momento di una lettura e l'applicazione ha una sola possibilità di gestirla.

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

chiudi()

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

long close(uint32_t handle_id);

[in] handle_id: handle per l'eliminazione

[retval]: 0 in caso di esito positivo; un errore negativo altrimenti

API Server

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

Metodi nell'API server

crea_porta()

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 deve essere univoco in tutto il sistema; i tentativi di creare un duplicato non andranno a buon fine.

[in] num_recv_bufs: il numero massimo di buffer su cui un canale questa porta può essere preallocata per facilitare lo scambio di dati con il client. I buffer vengono conteggiati separatamente per i dati che vanno in entrambe le direzioni. Quindi, specificando 1 qui, significa che sono preallocati un buffer di invio e un buffer di ricezione. In generale, il numero di buffer richiesta dipende dal protocollo di accordo di livello più alto tra il client e o server web. Nel caso di un protocollo molto sincrono, il numero può essere anche solo 1. (invia un messaggio, ricevi una risposta prima di inviarne un altro). Ma il numero può essere di più se il cliente prevede di inviare più di un messaggio prima che una risposta possa (ad es.un messaggio come prologo e un altro come comando effettivo). La I set di buffer allocati sono per canale, quindi due connessioni separate (canali) avrà set di buffer separati.

[in] recv_buf_size: dimensione massima di ogni singolo buffer nel oltre il buffer impostato. Questo valore è dipende dal protocollo e limita effettivamente le dimensioni massime dei messaggi che è possibile scambiare con il compagno

[in] flags: una combinazione di flag che specifica un comportamento aggiuntivo delle porte

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]: viene gestito con la porta creata se non è negativa o si verifica un errore specifico se negativo

Il server quindi interroga l'elenco degli handle delle porte per le connessioni in entrata con la chiamata wait(). Alla ricezione di una connessione richiesta indicata dal IPC_HANDLE_POLL_READY bit impostato nel campo event della struttura uevent_t, server deve chiamare accept() per completare la connessione e creare canale (rappresentato da un altro handle), che potrà quindi essere sottoposto a polling per i messaggi in arrivo.

accetta()

Accetta una connessione in entrata e ottiene un handle su un canale.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

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

[out] peer_uuid: indirizza a una struttura uuid_t da utilizzare Deve essere compilato con l'UUID dell'applicazione client che si connette. it verrà impostato su tutti gli zeri se la connessione proviene da un ambiente non sicuro

[retval]: consente di eseguire l'handle su 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 nell'API client.

Metodi nell'API client

connetti()

Avvia una connessione a una porta specificata dal nome.

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

[in] path: nome di una porta pubblicata da un'applicazione Trusty

[in] flags: specifica un comportamento aggiuntivo facoltativo

[retval]: consente di gestire un canale su cui i messaggi possono essere scambiati con server; errore se negativo

Se non viene specificato alcun valore flags (il parametro flags) è impostato su 0), la chiamata di connect() avvia una connessione sincrona a una porta specificata che restituisce un errore se la porta non esiste e crea un blocco fino a quando in caso contrario accetta una connessione.

Questo comportamento può essere modificato specificando una combinazione di due valori: descritti di seguito:

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

IPC_CONNECT_WAIT_FOR_PORT - forza un connect() chiamata di attesa se la porta specificata non esiste immediatamente al momento dell'esecuzione, anziché fallire immediatamente.

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

API Messaging

Le chiamate all'API Messaging consentono l'invio e la lettura dei messaggi su un connessione stabilita in precedenza (canale). Le chiamate all'API Messaging la stessa cosa per i server e i client.

Un cliente riceve un handle per un canale tramite l'emissione di un connect() e un server riceve l'handle di un canale da una chiamata accept(), descritti sopra.

Struttura di un messaggio affidabile

Come mostrato di seguito, i messaggi scambiati dall'API Trusty hanno una struttura, lasciando al server e al client un accordo sulla semantica 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 si distende e si raccoglie legge e scrive su questi blocchi utilizzando l'array iov. Il contenuto dei buffer che può essere descritto dall'array iov è completamente arbitrario.

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 al canale su cui inviare il messaggio

[in] msg: punta al ipc_msg_t structure che descrive il messaggio

[retval]: numero totale di byte inviati quando l'operazione riesce. un errore negativo altrimenti

Se il client (o il server) sta tentando di inviare un messaggio tramite il canale e non c'è spazio nella coda dei messaggi dei peer di destinazione, il canale potrebbe inserire uno stato di blocco dell'invio (questo non dovrebbe mai verificarsi per protocollo di richiesta e risposta, ma che potrebbero verificarsi in casi più complicati), ovvero indicato restituendo un codice di errore ERR_NOT_ENOUGH_BUFFER. In questo caso, il chiamante deve attendere che il compagno non liberi spazio nella sua coda di ricezione recuperando la gestione e ritirando i messaggi, indicato dal IPC_HANDLE_POLL_SEND_UNBLOCKED bit impostato il campo event della struttura uevent_t restituito dalla chiamata wait().

get_msg()

Riceve metainformazioni sul messaggio successivo in una coda di messaggi in arrivo

di un determinato canale.

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 la lunghezza totale di ogni messaggio viene compilata. Se configurato e consentito dal possono esserci più messaggi in sospeso (aperti) contemporaneamente per un canale specifico.

[retval]: NO_ERROR in caso di successo; un errore negativo altrimenti

read_msg()

Legge il contenuto del messaggio con l'ID specificato a partire dalla all'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: ID del messaggio da leggere

[in] offset: trova il messaggio da cui iniziare a leggere

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

[retval]: numero totale di byte archiviati nei buffer msg su successo; un errore negativo altrimenti

Il metodo read_msg può essere chiamato più volte a partire dal in modo diverso (non necessariamente sequenziale) in base alle esigenze.

metti_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 successo; un errore negativo altrimenti

Non è possibile accedere ai contenuti del messaggio dopo che il messaggio è stato ritirato e la il buffer occupato è stato liberato.

API File Descriptor

L'API File Descriptor include read(), write(), e ioctl() chiamate. Tutte queste chiamate possono operare su un insieme di file predefinito (statico) descrittori tradizionalmente rappresentati da piccoli numeri. Nel modello lo spazio del descrittore del file è separato dall'handle IPC spazio. L'API File Descriptor in Trusty è in modo simile a un'API tradizionale basata su descrittori di file.

Per impostazione predefinita, esistono tre descrittori di file predefiniti (standard e noti):

  • 0: input standard. L'implementazione predefinita dell'input standard fd è un'operazione autonoma (poiché non si prevede che le applicazioni attendibili console) quindi leggere, scrivere o richiamare ioctl() su fd 0 dovrebbe restituire un errore ERR_NOT_SUPPORTED.
  • 1: output standard. I dati scritti nell'output standard possono essere indirizzati (a seconda a livello di debug LK) a UART e/o a un log di memoria disponibile nella a seconda della piattaforma e della configurazione. Log di debug non critici e i messaggi vengono inseriti nell'output standard. read() e ioctl() non sono operativi e dovrebbero restituire un errore ERR_NOT_SUPPORTED.
  • 2: errore standard. I dati scritti su un errore standard devono essere indirizzati alla UART di memoria e di log disponibile sul lato non sicuro, a seconda della piattaforma configurazione. Si consiglia di scrivere solo i messaggi critici nei poiché è molto probabile che questo flusso non sia limitato. Le read() e I metodi ioctl() non sono operativi e dovrebbero restituire un errore ERR_NOT_SUPPORTED.

Anche se questo insieme di descrittori dei file può essere esteso per implementare fds (per implementare estensioni specifiche della piattaforma), estensione delle esigenze dei descrittori dei file devono essere esercitati con cautela. L'estensione dei descrittori dei file è soggetta alla creazione conflitti e non è generalmente consigliato.

Metodi nell'API File Descriptor

read()

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

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

[in] fd: descrittore del file da cui leggere

[out] buf: punta a un buffer in cui archiviare i dati

[in] count: numero massimo di byte da leggere

[retval]: restituito il numero di byte letti; un errore negativo altrimenti

write()

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

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

[in] fd: descrittore del file in cui scrivere

[out] buf: punta ai dati da scrivere

[in] count: numero massimo di byte da scrivere

[retval]: restituito il numero di byte scritti; un errore negativo altrimenti

ioctl()

Richiama un comando ioctl specificato per un determinato descrittore del file.

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

[in] fd: descrittore del file da cui richiamare ioctl()

[in] cmd: il comando ioctl

[in/out] args: punta a ioctl() argomenti

API varie

Metodi nell'API Miscellaneous

gettime()

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

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

[in] clock_id: a seconda della piattaforma; passa lo zero per il valore predefinito

[in] flags: riservato, deve essere zero

[out] time: punta a un valore int64_t in cui memorizzare l'ora attuale

[retval]: NO_ERROR in caso di successo; un errore negativo altrimenti

nanosleep()

Sospendi l'esecuzione dell'applicazione chiamante per un periodo di tempo specificato e la riprende dopo 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 sonno in nanosecondi

[retval]: NO_ERROR in caso di successo; un errore negativo altrimenti

Esempio di server delle applicazioni attendibile

L'applicazione di esempio seguente mostra l'utilizzo delle API indicate sopra. L'esempio crea un "eco" che gestisce più connessioni in entrata riflette al chiamante tutti i messaggi che riceve dai client originati dal lato sicuro o meno.

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

Il metodo run_end_to_end_msg_test() invia 10.000 messaggi in modo asincrono all'"echo" e gestire 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 applicazioni mondiali non sicure

Un insieme di servizi Trusty, pubblicati dal lato sicuro e contrassegnati con l'attributo IPC_PORT_ALLOW_NS_CONNECT, sono accessibili al kernel e i programmi dello spazio utente in esecuzione non sicuro.

L'ambiente di esecuzione sul lato non sicuro (kernel e spazio utente) è molto diverso dall'ambiente di esecuzione sul lato sicuro. Di conseguenza, anziché un'unica libreria per entrambi gli ambienti, sono disponibili due set di API differenti. Nel kernel, l'API client è fornita dalla driver kernel trusty-ipc e registra un nodo dispositivo a caratteri che può essere utilizzato dai processi dello spazio utente per comunicare con i servizi in esecuzione lato server.

API Trusty IPC Client dello spazio utente

La libreria API client Trusty IPC per lo spazio utente è un livello sottile nodo di dispositivo fd.

Un programma dello spazio utente avvia una sessione di comunicazione chiamando tipc_connect(), inizializzare una connessione a un servizio Trusty specificato. Internamente, la chiamata tipc_connect() apre un nodo di dispositivo specificato in ottiene un descrittore di file e richiama un TIPC_IOC_CONNECT ioctl() con il parametro argp che punta a una stringa contenente un il nome del servizio a cui connetterti.

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

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

Il descrittore del file ottenuto dalla chiamata tipc_connect() si comporta come un tipico nodo del dispositivo con caratteri; il descrittore del file:

  • Consente di passare alla modalità non di blocco, se necessario
  • Può essere scritto utilizzando un write() standard chiama per inviare messaggi all'altro lato
  • Possono essere intervistati (con chiamate poll() o select() chiamate) per la disponibilità dei messaggi in arrivo come normale descrittore di file
  • Possono essere letti per recuperare i messaggi in arrivo

Un chiamante invia un messaggio al servizio Trusty eseguendo una chiamata di scrittura per il valore fd specificato. Tutti i dati trasmessi alla chiamata write() sopra indicata viene trasformato in un messaggio dall'affidabile driver iCloud. Il messaggio è inviati al lato sicuro, dove i dati vengono gestiti dal sottosistema IPC al kernel Trusty, che viene instradato alla destinazione corretta e consegnato a un'app loop di eventi come evento IPC_HANDLE_POLL_MSG su un determinato canale . A seconda del caso specifico, specifico del servizio, il servizio Trusty può inviare una o più risposte di messaggi che vengono recapitati sul lato non sicuro e posizionati coda di messaggi del descrittore del file di canale appropriata applicazione spaziale read().

tipc_connect()

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

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

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

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

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

tipc_close()

Chiude la connessione al servizio Trusty specificato da un descrittore di file.

int tipc_close(int fd);

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

API client IPC Kernel Trusty

L'API client Trusty IPC del kernel è disponibile per i driver del kernel. L'utente L'API space Trusty IPC è implementata in base a questa API.

In generale, l'utilizzo tipico di questa API consiste nel fatto che un chiamante crea un oggetto struct tipc_chan mediante l'istruzione tipc_create_channel() e quindi utilizzando la chiamata tipc_chan_connect() per avviare una connessione al servizio Trusty IPC in esecuzione sul sistema lato server. La connessione al lato remoto può essere terminata chiamata a tipc_chan_shutdown() seguita da tipc_chan_destroy() per eseguire la pulizia delle risorse.

Dopo aver ricevuto una notifica (tramite la richiamata di handle_event()) stabilire una connessione, il chiamante le seguenti:

  • Ottiene un buffer dei messaggi utilizzando la chiamata tipc_chan_get_txbuf_timeout()
  • Scrive un messaggio e
  • Mette in coda il messaggio utilizzando tipc_chan_queue_msg() per la distribuzione a un servizio Trusty (sul lato sicuro), a cui il canale è collegato

Se l'accodamento è andato a buon fine, il chiamante deve dimenticare il buffer dei messaggi perché alla fine il buffer torna al pool di buffer libero dopo dall'elaborazione remota (per riutilizzarli in seguito, per altri messaggi). L'utente deve chiamare tipc_chan_put_txbuf() solo se non riesce a accoda questo buffer oppure quest'ultimo non è più necessario.

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

È previsto che il callback handle_msg() l'implementazione restituirà 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 una chiamata tipc_chan_get_rxbuf() se il buffer in entrata è in coda per ulteriori elaborazioni. È necessario monitorare un buffer rx scollegato e infine rilasciato usando una chiamata tipc_chan_put_rxbuf() non è più necessario.

Metodi nell'API client IPC Kernel Trusty

tipc_create_channel()

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

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

[in] dev: rimanda all'ipc affidabile per cui il dispositivo il canale viene creato

[in] ops: puntatore a struct tipc_chan_ops, con richieste specifiche richiami compilati

[in] cb_arg: puntatore ai dati che verranno trasmessi a tipc_chan_ops callback

[retval]: punta a un'istanza appena creata struct tipc_chan dopo l'esito positivo, ERR_PTR(err) altrimenti

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

L'evento void (*handle_event)(void *cb_arg, int event) è stato richiamato per informare un chiamante di un cambiamento di stato del canale.

[in] cb_arg: puntatore ai dati passati a un tipc_create_channel() chiamata

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

  • TIPC_CHANNEL_CONNECTED. Indica una connessione riuscita. sul lato remoto
  • TIPC_CHANNEL_DISCONNECTED: indica che il lato remoto ha negato il consenso nuova richiesta di connessione o richiesta disconnessione per il canale collegato in precedenza
  • TIPC_CHANNEL_SHUTDOWN: indica che il lato remoto è in fase di arresto, terminando definitivamente tutte le connessioni

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) viene richiamato per indicare che è stato inviato un nuovo messaggio ricevuti su un canale specificato:

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

tipc_chan_connect()

Avvia una connessione al servizio IPC Trusty specificato.

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

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

[in] port: punta a una stringa contenente la stringa nome servizio a cui connettersi

[retval]: 0 se l'operazione riesce, un errore negativo in caso contrario

Quando viene stabilita una connessione, il chiamante riceve una notifica Chiamata di handle_event.

tipc_chan_shutdown()

Termina una connessione al servizio IPC Trusty avviato in precedenza 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()

Elimina un canale IPC Trusty specificato.

void tipc_chan_destroy(struct tipc_chan *chan);

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

tipc_chan_get_txbuf_timeout()

Ottieni un buffer dei messaggi che può essere utilizzato per inviare dati attraverso un canale. 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: rimanda al canale al quale aggiungere un messaggio in coda

[in] chan: timeout massimo da attendere fino al Buffer tx disponibile

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

tipc_chan_queue_msg()

Mettere in coda un messaggio da inviare tramite l'indirizzo Canali IPC affidabili.

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

[in] chan: rimanda al canale al quale aggiungere il messaggio in coda

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

[retval]: 0 se l'operazione riesce, un errore negativo in caso contrario

tipc_chan_put_txbuf()

Rilascia il buffer dei messaggi Tx specificato ottenute in precedenza da una chiamata tipc_chan_get_txbuf_timeout().

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

[in] chan: indirizza al canale a cui vuoi indirizzare questo buffer appartiene

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

[retval]: nessuno

tipc_chan_get_rxbuf()

Ottieni un nuovo buffer dei messaggi che può essere utilizzato per ricevere messaggi tramite 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 dei messaggi

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

tipc_chan_put_rxbuf()

Rilascia un buffer dei messaggi specificato precedentemente ottenuto da un Chiamata tipc_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 dei messaggi

[in] mb: punta a un buffer dei messaggi da rilasciare

[retval]: nessuno