Referência da API Trusty

O Trusty fornece APIs para desenvolver duas classes de aplicativos/serviços:

  • Aplicativos ou serviços confiáveis executados no processador TEE
  • Aplicativos normais/confiáveis que são executados no processador principal e usam os serviços fornecidos por aplicativos confiáveis

The Trusty A API geralmente descreve o sistema Trusty de comunicação entre processos (IPC), incluindo comunicações com o mundo desprotegido. O software em execução no o processador principal pode usar as APIs Trusty para se conectar a aplicativos/serviços confiáveis e troque mensagens arbitrárias com eles como um serviço de rede por IP. Cabe ao aplicativo determinar o formato de dados e a semântica dessas usando um protocolo no nível do app. A entrega confiável de mensagens garantidos pela infraestrutura Trusty subjacente (na forma de drivers em execução no processador principal) e a comunicação é completamente assíncrona.

Portas e canais

As portas são usadas pelos aplicativos Trusty para expor os endpoints de serviço no formato de um caminho nomeado ao qual os clientes se conectam. Ela fornece uma string simples ID de serviço para os clientes usarem. A convenção de nomenclatura é usar o estilo DNS reverso nominal, por exemplo, com.google.servicename:

Quando um cliente se conecta a uma porta, ele recebe um canal para interagir com um serviço. O serviço precisa aceitar uma conexão de entrada e, quando recebe, ele também recebe um canal. Em essência, as portas são usadas para procurar serviços e, em seguida, a comunicação ocorre por dois canais conectados (ou seja, instâncias de conexão em uma porta). Quando um cliente se conecta a uma porta, simétrica, uma conexão bidirecional é estabelecida. Com esse caminho full-duplex, os clientes e os servidores podem trocar mensagens arbitrárias até que um dos lados decida romper a conexão.

Somente aplicativos confiáveis do lado da segurança ou módulos do kernel Trusty podem criar portas. Os aplicativos executados no lado não seguro (no mundo normal) podem apenas a serviços publicados pela segurança.

Dependendo dos requisitos, um aplicativo confiável pode ser um cliente e um ao mesmo tempo. Um aplicativo confiável que publica um serviço (como um servidor) pode precisar se conectar a outros serviços (como cliente).

Processar API

Identificadores são números inteiros não assinados que representam recursos como portas e canais, semelhante aos descritores de arquivo em UNIX. Depois que os identificadores são criados, são colocadas em uma tabela de identificador específica do aplicativo e podem ser referenciadas mais tarde.

Para associar dados particulares a um identificador, o autor da chamada pode usar o método set_cookie().

Métodos na API Handle

Os identificadores só são válidos no contexto de um aplicativo. Um aplicativo precisa não passar o valor de um identificador para outros aplicativos, a menos que explicitamente especificado. Um valor do identificador só deve ser interpretado comparando-o com o INVALID_IPC_HANDLE #define, que um aplicativo pode usar como indicação de que um identificador é inválido ou não foi definido.

Associa os dados particulares fornecidos pelo autor da chamada a um identificador especificado.

long set_cookie(uint32_t handle, void *cookie)

[in] handle: qualquer identificador retornado por uma das chamadas de API

[in] cookie: ponteiro para dados arbitrários do espaço do usuário no aplicativo Trusty

[retval]: NO_ERROR em caso de sucesso, < 0 código de erro caso contrário

Essa chamada é útil para lidar com eventos quando eles ocorrem mais tarde o identificador foi criado. O mecanismo de tratamento de eventos fornece a alça e o cookie de volta ao manipulador de eventos.

É possível aguardar os identificadores usando a chamada wait().

espera()

Aguarda um evento em um determinado identificador por um período especificado.

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

[in] handle_id: qualquer identificador retornado por uma das chamadas de API

[out] event: um ponteiro para a estrutura que representa um evento que ocorreu neste identificador

[in] timeout_msecs: um valor de tempo limite em milissegundos. por o valor de -1 é um tempo limite infinito

[retval]: NO_ERROR se um evento válido tiver ocorrido em um intervalo de tempo limite especificado. ERR_TIMED_OUT se um tempo limite especificado tiver decorrido, mas não ocorreu; < 0 para outros erros

Com a conclusão (retval == NO_ERROR), a chamada wait() preenche uma estrutura uevent_t especificada com informações sobre o evento que ocorreu.

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;

O campo event contém uma combinação dos seguintes valores:

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: nenhum evento está pendente. o autor da chamada precisa reiniciar a espera

IPC_HANDLE_POLL_ERROR: ocorreu um erro interno não especificado.

IPC_HANDLE_POLL_READY - depende do tipo de identificador, da seguinte maneira:

  • Para portas, este valor indica que há uma conexão pendente
  • Para canais, este valor indica que uma conexão assíncrona (consulte connect()) foi estabelecido

Os eventos a seguir são relevantes apenas para canais:

  • IPC_HANDLE_POLL_HUP: indica que um canal foi fechado por um app semelhante
  • IPC_HANDLE_POLL_MSG: indica que há uma mensagem pendente para o canal.
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - indica que uma resposta autor da chamada bloqueado por envio pode tentar enviar uma mensagem novamente (consulte a descrição de send_msg() para mais detalhes)

Um manipulador de eventos deve estar preparado para lidar com uma combinação de já que vários bits podem ser definidos ao mesmo tempo. Por exemplo, para um pode haver mensagens pendentes e uma conexão encerrada por um ao mesmo tempo.

A maioria dos eventos é fixa. Elas persistem enquanto a condição subjacente persistem (por exemplo, todas as mensagens pendentes são recebidas e a conexão pendente solicitações são tratadas). A exceção é o caso o evento IPC_HANDLE_POLL_SEND_UNBLOCKED, que é apagado após uma leitura e o aplicativo tem apenas uma chance de lidar com isso.

Os identificadores podem ser destruídos chamando o método close().

Fechar()

Destrói o recurso associado ao identificador especificado e o remove do na tabela de alças.

long close(uint32_t handle_id);

[in] handle_id: identificador para destruir

[retval]: 0 se for bem-sucedido; um erro negativo, caso contrário

API do servidor

O servidor começa criando uma ou mais portas nomeadas que representam os endpoints de serviço. Cada porta é representada por um identificador.

Métodos na API do servidor

Função porta_create()

Cria uma porta de serviço nomeada.

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

[in] path: o nome da string da porta (conforme descrito acima). Isso nome deve ser único em todo o sistema. as tentativas de criar uma cópia vão falhar.

[in] num_recv_bufs: o número máximo de buffers em que um canal essa porta pode ser pré-alocada para facilitar a troca de dados com o cliente. Os buffers são contados separadamente para dados indo em ambas as direções, portanto, especificar 1 aqui significa 1 send e 1 recebe buffer são pré-alocados. Em geral, o número de buffers necessário depende do contrato de protocolo de nível superior entre o cliente e servidor. O número pode ser apenas 1 no caso de um protocolo muito síncrono (enviar mensagem, receber resposta antes de enviar outra). Mas o número pode ser mais se o cliente espera enviar mais de uma mensagem antes que uma resposta possa aparecer (por exemplo, uma mensagem como um prólogo e outra como o comando real). A conjuntos de buffers alocados são por canal, portanto, duas conexões separadas (canais) teria conjuntos de buffers separados.

[in] recv_buf_size: tamanho máximo de cada buffer individual no acima do conjunto de buffers. Esse valor é dependente de protocolo e limita com eficácia o tamanho máximo de mensagens que pode ser trocado com peering

[in] flags: uma combinação de sinalizações que especifica o comportamento adicional da porta.

Esse valor precisa ser uma combinação dos seguintes valores:

IPC_PORT_ALLOW_TA_CONNECT: permite uma conexão de outros apps seguros

IPC_PORT_ALLOW_NS_CONNECT: permite uma conexão de um mundo não seguro.

[retval]: gerencia para a porta criada se não for negativa ou para um erro específico se negativos

Em seguida, o servidor consulta a lista de identificadores de portas em busca de conexões de entrada. usando a chamada wait(). Ao receber uma conexão solicitação indicada pelo bit IPC_HANDLE_POLL_READY definido no o campo event da estrutura uevent_t, a servidor deve chamar accept() para terminar de estabelecer uma conexão e criar uma canal (representado por e outro identificador) que, depois, podem pesquisar mensagens recebidas.

Aceitar()

Aceita uma conexão de entrada e recebe um identificador para um canal.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id: identificador que representa a porta a que um cliente se conectou

[out] peer_uuid: ponteiro para uma estrutura de uuid_t a ser preenchido com o UUID do aplicativo cliente conectado. Ela será definido como zero se a conexão tiver origem em um mundo não seguro

[retval]: identificador para um canal (se não negativo) em que o servidor pode trocar mensagens com o cliente (ou um código de erro, caso contrário).

API do cliente

Esta seção contém os métodos na API Client.

Métodos na API Client

Conectar()

Inicia uma conexão com uma porta especificada por nome.

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

[in] path: nome de uma porta publicada por um aplicativo Trusty

[in] flags: especifica um comportamento adicional e opcional.

[retval]: identificador para um canal em que as mensagens podem ser trocadas com o servidor erro, se negativo

Se nenhum flags for especificado (o parâmetro flags) estiver definido como 0), chamar connect() inicia uma conexão síncrona para uma porta especificada que imediatamente retorna um erro se a porta não existir e cria um bloco até que o servidor aceita uma conexão.

Esse comportamento pode ser alterado especificando uma combinação de dois valores, descritas abaixo:

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

IPC_CONNECT_WAIT_FOR_PORT: força uma connect(). para aguardar se a porta especificada não existir imediatamente na execução, em vez de falhar imediatamente.

IPC_CONNECT_ASYNC: se definido, inicia uma conexão assíncrona. Um aplicativo precisa procurar o identificador retornado (chamando wait() para um evento de conclusão de conexão indicado por IPC_HANDLE_POLL_READY bit definido no campo de evento da estrutura uevent_t antes de começar operação normal.

API Messaging

As chamadas da API Messaging permitem o envio e a leitura de mensagens por uma conexão (canal) previamente estabelecida. As chamadas da API Messaging são as o mesmo para servidores e clientes.

Um cliente recebe um identificador para um canal emitindo um connect() e um servidor recebe um identificador de canal de uma chamada accept(), descritas acima.

Estrutura de uma mensagem confiável

Como mostrado abaixo, as mensagens trocadas pela API Trusty têm um mínimo do servidor, deixando que o servidor e o cliente cheguem a um acordo sobre a semântica do conteúdos reais:

/*
 *  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;

Uma mensagem pode ser composta por um ou mais buffers não contíguos representados por uma matriz de estruturas iovec_t. Trusty se divertindo durante a jornada faz leituras e gravações nesses blocos usando a matriz iov. O conteúdo dos buffers que pode ser descrito da matriz iov é completamente arbitrário.

Métodos na API Messaging

send_msg()

Envia uma mensagem por um canal específico.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle: identificador para o canal que vai receber a mensagem

[in] msg: ponteiro para o ipc_msg_t structure que descreve a mensagem

[retval]: número total de bytes enviados em caso de sucesso; um erro negativo, caso contrário

Se o cliente (ou servidor) está tentando enviar uma mensagem pelo canal e não houver espaço na fila de mensagens de mesmo nível de destino, o canal poderá entram em um estado de bloqueio de envio. Isso nunca deve acontecer de solicitação/resposta, mas pode acontecer em casos mais complicados), isso é indicado pelo retorno de um código de erro ERR_NOT_ENOUGH_BUFFER. Nesse caso, o autor da chamada deve aguardar até que o par libere alguns na fila de recebimento, recuperando as mensagens de processamento e indicado pelo bit IPC_HANDLE_POLL_SEND_UNBLOCKED definido no o campo event da estrutura uevent_t retornados pela chamada de wait().

get_msg().

Recebe metainformações sobre a próxima mensagem em uma fila de mensagens recebidas

de um canal específico.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle: identificador do canal em que uma nova mensagem precisa ser recuperada

[out] msg_info: estrutura de informações da mensagem descrita da seguinte maneira:

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

Cada mensagem recebe um ID exclusivo no conjunto de mensagens pendentes, e o tamanho total de cada mensagem é preenchido. Se configurado e permitido pelo do Google, pode haver várias mensagens pendentes (abertas) de uma só vez por um canal específico.

[retval]: NO_ERROR em caso de sucesso; um erro negativo, caso contrário

read_msg().

Lê o conteúdo da mensagem com o ID especificado a partir do deslocamento especificado.

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

[in] handle: identificador do canal em que a mensagem será lida

[in] msg_id: ID da mensagem a ser lida

[in] offset: deslocamento da mensagem de onde começar a leitura

[out] msg: ponteiro para a estrutura ipc_msg_t que descreve um conjunto de buffers para armazenar a mensagem de entrada dados

[retval]: número total de bytes armazenados nos buffers msg em sucesso um erro negativo, caso contrário

O método read_msg pode ser chamado várias vezes, a partir das um diferente (não necessariamente sequencial) e ajustado conforme necessário.

put_msg()

Desativa uma mensagem com um ID especificado.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle: identificador do canal em que a mensagem chegou

[in] msg_id: ID da mensagem que será desativada

[retval]: NO_ERROR em caso de sucesso; um erro negativo, caso contrário

Não é possível acessar o conteúdo da mensagem após a remoção dela e a que ocupou foi liberado.

API File Descriptor

A API File Descriptor inclui read(), write(), e chamadas de ioctl(). Todas essas chamadas podem operar em um conjunto predefinido (estático) de arquivos descritores tradicionalmente representados por números pequenos. Na atualidade implementação, o espaço do descritor do arquivo é separado do identificador da IPC espaço. A API File Descriptor no Trusty é semelhante a uma API tradicional baseada em descritor de arquivo.

Por padrão, há três descritores de arquivo predefinidos (padrão e conhecidos):

  • 0 - entrada padrão. A implementação padrão da entrada padrão fd é um ambiente autônomo (já que os aplicativos confiáveis não devem ter uma interface console), portanto, ler, gravar ou invocar ioctl() em fd 0 vai retornar um erro ERR_NOT_SUPPORTED.
  • 1: saída padrão. Os dados gravados na saída padrão podem ser roteados (dependendo no nível de depuração LK) para UART e/ou um registro de memória disponível no ambiente dependendo da plataforma e da configuração. Registros de depuração não críticos e as mensagens devem ficar na saída padrão. read() e ioctl() são inativos e precisam retornar um erro ERR_NOT_SUPPORTED.
  • 2 - erro padrão. Dados gravados no erro padrão devem ser roteados para o UART ou registro de memória disponível no lado não seguro, dependendo da plataforma e configuração do Terraform. É recomendável escrever apenas mensagens importantes no porque é muito provável que esse fluxo não seja limitado. Os métodos read() e Os métodos ioctl() não são operacionais e precisam retornar um erro ERR_NOT_SUPPORTED.

Embora esse conjunto de descritores de arquivo possa ser estendido para implementar mais fds (para implementar extensões específicas da plataforma), a extensão das necessidades dos descritores de arquivo devem ser exercidos com cautela. A extensão dos descritores de arquivo é propensa a criar conflitantes, o que geralmente não é recomendado.

Métodos na API File Descriptor

read().

Tenta ler até count bytes de dados de um descritor de arquivo especificado.

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

[in] fd: descritor do arquivo a partir do qual ler

[out] buf: ponteiro para um buffer em que os dados são armazenados.

[in] count: número máximo de bytes para leitura

[retval]: número retornado de bytes lidos; um erro negativo, caso contrário

write()

Grava até count bytes de dados no descritor de arquivo especificado.

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

[in] fd: descritor do arquivo no qual gravar

[out] buf: ponteiro para os dados a serem gravados

[in] count: número máximo de bytes para gravação

[retval]: número retornado de bytes gravados; um erro negativo, caso contrário

Função ioctl

Invoca um comando ioctl especificado para um determinado descritor de arquivo.

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

[in] fd: descritor do arquivo em que o ioctl() será invocado

[in] cmd: o comando ioctl

[entrada/saída] args: ponteiro para argumentos ioctl().

API Miscellaneous

Métodos na API Miscellaneous

gettime().

Retorna o horário atual do sistema (em nanossegundos).

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

[in] clock_id: dependente da plataforma. passe zero para valores padrão

[in] flags: reservado, precisa ser zero

[out] time: ponteiro para um valor int64_t em que o horário atual será armazenado.

[retval]: NO_ERROR em caso de sucesso; um erro negativo, caso contrário

nanosleep().

Suspende a execução do aplicativo de chamada por um período específico e a retoma após esse período.

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

[in] clock_id: reservado, precisa ser zero

[in] flags: reservado, precisa ser zero

[in] sleep_time: tempo de suspensão em nanossegundos

[retval]: NO_ERROR em caso de sucesso; um erro negativo, caso contrário

Exemplo de um servidor de aplicativos confiável

O exemplo de aplicativo a seguir mostra o uso das APIs acima. O exemplo cria um "eco" que lida com várias conexões de entrada e reflete para o autor da chamada todas as mensagens que ele recebe dos clientes originadas não seja segura ou não.

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

O método run_end_to_end_msg_test() envia 10.000 mensagens de forma assíncrona. ao "echo" serviço e identificadores respostas

static int run_echo_test(void)
{
  int rc;
  handle_t chan;
  uevent_t uevt;
  uint8_t tx_buf[64];
  uint8_t rx_buf[64];
  ipc_msg_info_t inf;
  ipc_msg_t   tx_msg;
  iovec_t     tx_iov;
  ipc_msg_t   rx_msg;
  iovec_t     rx_iov;

  /* prepare tx message buffer */
  tx_iov.base = tx_buf;
  tx_iov.len  = sizeof(tx_buf);
  tx_msg.num_iov = 1;
  tx_msg.iov     = &tx_iov;
  tx_msg.num_handles = 0;
  tx_msg.handles = NULL;

  memset (tx_buf, 0x55, sizeof(tx_buf));

  /* prepare rx message buffer */
  rx_iov.base = rx_buf;
  rx_iov.len  = sizeof(rx_buf);
  rx_msg.num_iov = 1;
  rx_msg.iov     = &rx_iov;
  rx_msg.num_handles = 0;
  rx_msg.handles = NULL;

  /* open connection to echo service */
  rc = sync_connect(srv_name, 1000);
  if(rc < 0)
    return rc;

  /* got channel */
  chan = (handle_t)rc;

  /* send/receive 10000 messages asynchronously. */
  uint tx_cnt = 10000;
  uint rx_cnt = 10000;

  while (tx_cnt || rx_cnt) {
    /* send messages until all buffers are full */
while (tx_cnt) {
    rc = send_msg(chan, &tx_msg);
      if (rc == ERR_NOT_ENOUGH_BUFFER)
      break;  /* no more space */
    if (rc != 64) {
      if (rc > 0) {
        /* incomplete send */
        rc = ERR_NOT_VALID;
}
      goto abort_test;
}
    tx_cnt--;
  }

  /* wait for reply msg or room */
  rc = wait(chan, &uevt, 1000);
  if (rc != NO_ERROR)
    goto abort_test;

  /* drain all messages */
  while (rx_cnt) {
    /* get a reply */
      rc = get_msg(chan, &inf);
    if (rc == ERR_NO_MSG)
        break;  /* no more messages  */
  if (rc != NO_ERROR)
goto abort_test;

  /* read reply data */
    rc = read_msg(chan, inf.id, 0, &rx_msg);
  if (rc != 64) {
    /* unexpected reply length */
    rc = ERR_NOT_VALID;
    goto abort_test;
}

  /* discard reply */
  rc = put_msg(chan, inf.id);
  if (rc != NO_ERROR)
    goto abort_test;
  rx_cnt--;
  }
}

abort_test:
  close(chan);
  return rc;
}

APIs e aplicativos de mundo não seguro

Um conjunto de serviços Trusty, publicados pela segurança e marcados com o atributo IPC_PORT_ALLOW_NS_CONNECT, são acessíveis ao kernel e programas de espaço do usuário executados não seguro.

O ambiente de execução no lado não seguro (kernel e espaço do usuário) é muito diferente do ambiente de execução de segurança. Portanto, em vez de uma única biblioteca para os dois ambientes, há duas diferentes conjuntos de APIs. No kernel, a API do cliente é fornecida pelo driver do kernel trusty-ipc e registra um nó de dispositivo de caractere que pode ser usado por processos de espaço do usuário para se comunicar com os serviços executados no lado.

API User Space Trusty IPC Client

A biblioteca de API Trusty IPC Client do espaço do usuário é uma camada fina sobre a nó do dispositivo fd.

Um programa espacial do usuário inicia uma sessão de comunicação chame tipc_connect(), inicializar uma conexão com um serviço Trusty especificado. Internamente, a chamada tipc_connect() abre um nó de dispositivo especificado para recebe um descritor de arquivo e invoca um TIPC_IOC_CONNECT ioctl() chamada com o parâmetro argp que aponta para uma string contendo um nome do serviço ao qual se conectar.

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

O descritor de arquivo resultante só pode ser usado para comunicação com o serviço para a qual ele foi criado. O descritor do arquivo deve ser fechado chamar tipc_close() quando a conexão não for mais necessária.

O descritor de arquivo recebido pela chamada tipc_connect() comporta-se como um nó de dispositivo de personagem típico; o descritor do arquivo:

  • Pode ser alternado para o modo sem bloqueio, se necessário
  • Pode ser gravado usando um write() padrão. ligue para enviar mensagens para o outro lado
  • Pode ser pesquisado (usando chamadas poll() ou select()) para disponibilidade de mensagens recebidas como um descritor de arquivo regular
  • Pode ser lido para recuperar mensagens recebidas

O autor da chamada envia uma mensagem ao serviço Trusty executando uma chamada de gravação para o fd especificado. Todos os dados transmitidos para a chamada write() acima é transformado em uma mensagem pelo driver trusty-ipc. A mensagem é entregues ao lado seguro, no qual os dados são manipulados pelo subsistema IPC em no kernel Trusty, encaminhado para o destino adequado e entregue a um aplicativo loop de eventos como um evento IPC_HANDLE_POLL_MSG em um canal específico cabo Dependendo das necessidades protocolo específico do serviço, o serviço Trusty pode enviar uma ou mais mensagens que são devolvidas ao lado desprotegido e colocadas no fila de mensagens do descritor do arquivo de canal apropriado a ser recuperada pelo usuário chamada read() do aplicativo espacial.

tipc_connect()

Abre um nó de dispositivo tipc especificado e inicia um conexão com um serviço Trusty especificado.

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

[in] dev_name: caminho para o nó do dispositivo IPC Trusty a ser aberto

[in] srv_name: nome de um serviço Trusty publicado ao qual se conectar

[retval]: descritor de arquivo válido em caso de sucesso; caso contrário, será -1.

tipc_close()

Fecha a conexão com o serviço Trusty especificado por um descritor de arquivo.

int tipc_close(int fd);

[in] fd: descritor de arquivo aberto anteriormente por uma chamada de tipc_connect()

API Kernel Trusty IPC Client

A API Trusty IPC Client do kernel está disponível para drivers de kernel. O usuário Space Trusty IPC API é implementada com base nessa API.

Em geral, o uso típico desta API consiste em um autor da chamada que cria um objeto struct tipc_chan usando o tipc_create_channel() e, em seguida, usando a chamada tipc_chan_connect() para iniciar uma ao serviço Trusty IPC em execução no servidor seguro lado. A conexão com o lado remoto pode ser encerrada pela chamando tipc_chan_shutdown() seguido de tipc_chan_destroy() para limpar os recursos.

Ao receber uma notificação (pelo callback handle_event()) que uma conexão foi estabelecida, o autor da chamada o seguinte:

  • Recebe um buffer de mensagens usando a chamada tipc_chan_get_txbuf_timeout().
  • escrever uma mensagem;
  • coloca a mensagem na fila usando o tipc_chan_queue_msg(); método de entrega a um serviço Trusty (pelo lado seguro), ao qual o O canal está conectado

Depois que o enfileiramento for bem-sucedido, o autor da chamada deverá esquecer o buffer de mensagens porque o buffer de mensagem finalmente retorna ao pool de buffers livre após processamento pelo lado remoto (para reutilização posterior, para outras mensagens). O usuário só precisa chamar tipc_chan_put_txbuf() se ele não enfileirar esse buffer ou ele não será mais necessário.

Um usuário da API recebe mensagens do lado remoto ao processar uma Callback da notificação handle_msg() (que é chamado o contexto da fila de trabalho rx trusty-ipc) que fornece um ponteiro para um buffer rx que contém um mensagem recebida seja tratada.

Espera-se que o callback handle_msg() vai retornar um ponteiro para um struct tipc_msg_buf válido. Pode ser igual ao buffer de mensagens recebidas se for processado localmente e não é mais necessário. Alternativamente, pode ser um novo buffer obtido pelo uma chamada tipc_chan_get_rxbuf() se o buffer recebido estiver na fila para processamento adicional. Um buffer rx removido precisa ser rastreado e acabou sendo lançado usando uma chamada tipc_chan_put_rxbuf() quando ele não é mais necessário.

Métodos na API Kernel Trusty IPC Client

tipc_create_channel()

Cria e configura uma instância de um canal de IPC confiável para um determinado confiável.

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

[in] dev: ponteiro para o IPC confiável de que o dispositivo. canal é criado

[in] ops: ponteiro para um struct tipc_chan_ops. com configurações específicas do autor da chamada callbacks preenchidos

[in] cb_arg: ponteiro para os dados que serão transmitidos para callbacks de tipc_chan_ops

[retval]: ponteiro para uma instância recém-criada de struct tipc_chan em caso de sucesso, Caso contrário, ERR_PTR(err)

Em geral, o autor da chamada precisa fornecer dois callbacks invocados de forma assíncrona. quando a atividade correspondente está ocorrendo.

O evento void (*handle_event)(void *cb_arg, int event) é invocado para notificar o autor da chamada sobre uma mudança no estado do canal.

[in] cb_arg: ponteiro para os dados transmitidos para um tipc_create_channel() chamada

[in] event: um evento que pode ser um dos seguintes valores:

  • TIPC_CHANNEL_CONNECTED: indica uma conexão bem-sucedida. Para o lado remoto
  • TIPC_CHANNEL_DISCONNECTED: indica que o lado remoto negou a uma nova solicitação de conexão desconexão do canal conectado anteriormente
  • TIPC_CHANNEL_SHUTDOWN: indica que o lado remoto está sendo desligado. encerrar permanentemente todas as conexões

O struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) é invocado para fornecer uma notificação de que uma nova mensagem foi recebidos em um canal específico:

  • [in] cb_arg: ponteiro para os dados transmitidos ao tipc_create_channel() chamada
  • [in] mb: ponteiro para um struct tipc_msg_buf que descreve uma mensagem recebida
  • [retval]: Espera-se que a implementação de callback retorne um ponteiro para uma struct tipc_msg_buf, que podem ser o mesmo ponteiro recebido como o parâmetro mb se a mensagem for tratada localmente e não for mais necessário (ou pode ser um novo buffer recebido pela chamada tipc_chan_get_rxbuf())

tipc_chan_connect()

Inicia uma conexão com o serviço Trusty IPC especificado.

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

[in] chan: ponteiro para um canal retornado pelo tipc_create_chan() chamada

[in] port: ponteiro para uma string contendo o nome do serviço ao qual se conectar

[retval]: 0 em caso de sucesso; caso contrário, será um erro negativo

O autor da chamada é notificado quando uma conexão é estabelecida recebendo um handle_event.

tipc_chan_shutdown()

Encerra uma conexão com o serviço Trusty IPC iniciado anteriormente por uma chamada de tipc_chan_connect().

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan: ponteiro para um canal retornado por uma chamada de tipc_create_chan()

tipc_chan_destroy()

Destrói um canal Trusty IPC especificado.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan: ponteiro para um canal retornado pelo tipc_create_chan() chamada

tipc_chan_get_txbuf_timeout()

Recebe um buffer de mensagem que pode ser usado para enviar dados em uma canal. Se o buffer não estiver disponível imediatamente, o autor da chamada poderá ser bloqueado para o tempo limite especificado (em milissegundos).

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

[in] chan: ponteiro para o canal em que uma mensagem será enfileirada

[in] chan: tempo limite máximo para aguardar até o O buffer tx fica disponível

[retval]: um buffer de mensagem válido em caso de sucesso, ERR_PTR(err) com erro

tipc_chan_queue_msg()

Coloca na fila uma mensagem a ser enviada no Canais IPC confiáveis.

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

[in] chan: ponteiro para o canal em que a mensagem será enfileirada

[in] mb: Ponteiro da mensagem que será enfileirada (recebido por uma chamada tipc_chan_get_txbuf_timeout())

[retval]: 0 em caso de sucesso; caso contrário, será um erro negativo

tipc_chan_put_txbuf()

Libera o buffer de mensagens Tx especificado recebido anteriormente por uma chamada tipc_chan_get_txbuf_timeout().

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

[in] chan: ponteiro para o canal ao qual este buffer de mensagem pertence

[in] mb: ponteiro para o buffer de mensagem a ser liberado

[retval]: nenhum

tipc_chan_get_rxbuf()

Recebe um novo buffer de mensagens que pode ser usado para receber mensagens pela canal especificado.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan: ponteiro para um canal ao qual esse buffer de mensagem pertence

[retval]: um buffer de mensagem válido em caso de sucesso, ERR_PTR(err) em caso de erro

tipc_chan_put_rxbuf()

Libera um buffer de mensagens especificado obtido anteriormente por um tipc_chan_get_rxbuf().

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

[in] chan: ponteiro para um canal ao qual esse buffer de mensagem pertence

[in] mb: ponteiro para um buffer de mensagem a ser liberado

[retval]: nenhum