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.
set_cookie()
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 semelhanteIPC_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 desend_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 invocarioctl()
emfd
0 vai retornar um erroERR_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()
eioctl()
são inativos e precisam retornar um erroERR_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étodosioctl()
não são operacionais e precisam retornar um erroERR_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()
ouselect()
) 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 remotoTIPC_CHANNEL_DISCONNECTED
: indica que o lado remoto negou a uma nova solicitação de conexão desconexão do canal conectado anteriormenteTIPC_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 aotipc_create_channel()
chamada - [in]
mb
: ponteiro para umstruct 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âmetromb
se a mensagem for tratada localmente e não for mais necessário (ou pode ser um novo buffer recebido pela chamadatipc_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