Trusty proporciona APIs para desarrollar dos clases de apps y servicios:
- Apps y servicios de confianza que se ejecutan en el procesador TEE
- Apps normales y no confiables que se ejecutan en el procesador principal y usan los servicios que proporcionan las apps confiables
La API de Trusty generalmente describe el sistema de comunicación entre procesos (IPC) de Trusty, incluidas las comunicaciones con el mundo no seguro. El software que se ejecuta en el procesador principal puede usar las APIs de Trusty para conectarse a apps y servicios de confianza, y también intercambiar mensajes arbitrarios con ellos, al igual que un servicio de red a través de IP. Depende de la app determinar el formato de datos y la semántica de estos mensajes con un protocolo a nivel de la app. La infraestructura subyacente de Trusty (en forma de controladores que se ejecutan en el procesador principal) garantiza la entrega confiable de los mensajes, y la comunicación es completamente asíncrona.
Puertos y canales
Las apps de confianza usan puertos para exponer extremos de servicio en forma de una ruta de acceso con nombre a la que se conectan los clientes. Esto proporciona un ID de servicio simple basado en cadenas para que lo usen los clientes. La convención de nombres es de estilo DNS inverso, p.ej., com.google.servicename
.
Cuando un cliente se conecta a un puerto, recibe un canal para interactuar con un servicio. El servicio debe aceptar una conexión entrante y, cuando lo hace, también recibe un canal. En esencia, los puertos se usan para buscar servicios y, luego, la comunicación se realiza a través de un par de canales conectados (es decir, instancias de conexión en un puerto). Cuando un cliente se conecta a un puerto, se establece una conexión simétrica y bidireccional. Con esta ruta de doble dúplex, los clientes y los servidores pueden intercambiar mensajes arbitrarios hasta que cualquiera de las partes decida cerrar la conexión.
Solo las apps de confianza del lado seguro o los módulos del kernel de Trusty pueden crear puertos. Las apps que se ejecutan en el lado no seguro (en el mundo normal) solo pueden conectarse a los servicios publicados por el lado seguro.
Según los requisitos, una app de confianza puede ser un cliente y un servidor al mismo tiempo. Es posible que una app de confianza que publica un servicio (como un servidor) deba conectarse a otros servicios (como un cliente).
API de Handle
Los identificadores son números enteros sin firmar que representan recursos, como puertos y canales, similares a los descriptores de archivos en UNIX. Después de crear los controladores, se colocan en una tabla de controladores específica de la app y se puede hacer referencia a ellos más adelante.
Un llamador puede asociar datos privados con un identificador mediante el método set_cookie()
.
Métodos de la API de Handle
Los identificadores solo son válidos en el contexto de una app. Una app no debe pasar el valor de un identificador a otras apps, a menos que se especifique de forma explícita. Un valor de control solo debe interpretarse comparándolo con el INVALID_IPC_HANDLE #define,
que una app puede usar como una indicación de que un control no es válido o no se configuró.
set_cookie()
Asocia los datos privados proporcionados por el llamador con un identificador especificado.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: Cualquier controlador que devuelva una de las llamadas a la API
[in] cookie
: Puntero a datos arbitrarios del espacio de usuario en la app de Trusty
[retval]: NO_ERROR
si se realiza correctamente, < 0
código de error de lo contrario
Esta llamada es útil para controlar eventos cuando ocurren más adelante después de que se creó el control. El mecanismo de control de eventos le proporciona el control y su cookie al controlador de eventos.
Se pueden esperar eventos en los controladores con la llamada wait()
.
wait()
Espera a que se produzca un evento en un controlador determinado durante un período determinado.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: Cualquier controlador que devuelva una de las llamadas a la API
[out] event
: Es un puntero a la estructura que representa un evento que se produjo en este control.
[in] timeout_msecs
: Es un valor de tiempo de espera en milisegundos. Un valor de -1 es un tiempo de espera infinito.
[retval]: NO_ERROR
si se produjo un evento válido dentro de un intervalo de tiempo de espera especificado; ERR_TIMED_OUT
si transcurrió un tiempo de espera especificado, pero no se produjo ningún evento; < 0
para otros errores
Cuando se realiza correctamente (retval == NO_ERROR
), la llamada a wait()
completa una estructura uevent_t
especificada con información sobre el evento que se produjo.
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;
El campo event
contiene una combinación de los siguientes 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
: No hay eventos pendientes, el llamador debe reiniciar la espera.
IPC_HANDLE_POLL_ERROR
: Se produjo un error interno no especificado.
IPC_HANDLE_POLL_READY
: Depende del tipo de control, como se indica a continuación:
- En el caso de los puertos, este valor indica que hay una conexión pendiente.
- En el caso de los canales, este valor indica que se estableció una conexión asíncrona (consulta
connect()
).
Los siguientes eventos solo son relevantes para los canales:
IPC_HANDLE_POLL_HUP
: Indica que un par cerró un canal.IPC_HANDLE_POLL_MSG
: Indica que hay un mensaje pendiente para este canal.IPC_HANDLE_POLL_SEND_UNBLOCKED
: Indica que un emisor bloqueado anteriormente puede intentar volver a enviar un mensaje (consulta la descripción desend_msg()
para obtener más información).
Un controlador de eventos debe estar preparado para controlar una combinación de eventos especificados, ya que se pueden establecer varios bits al mismo tiempo. Por ejemplo, para un canal, es posible tener mensajes pendientes y que un par cierre una conexión al mismo tiempo.
La mayoría de los eventos son persistentes. Persisten mientras persiste la condición subyacente (por ejemplo, se reciben todos los mensajes pendientes y se controlan las solicitudes de conexión pendientes). La excepción es el caso del evento IPC_HANDLE_POLL_SEND_UNBLOCKED
, que se borra después de una lectura y la app solo tiene una oportunidad para controlarlo.
Para destruir los controladores, llama al método close()
.
close()
Destruye el recurso asociado con el identificador especificado y lo quita de la tabla de identificadores.
long close(uint32_t handle_id);
[in] handle_id
: Controlador para destruir
[retval]: 0 si se realiza correctamente; un error negativo de lo contrario
API del servidor
Un servidor comienza por crear uno o más puertos con nombre que representan sus extremos de servicio. Cada puerto está representado por un identificador.
Métodos en la API del servidor
port_create()
Crea un puerto de servicio con nombre.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: Es el nombre de cadena del puerto (como se describió anteriormente). Este nombre debe ser único en todo el sistema. Los intentos de crear un duplicado fallan.
[in] num_recv_bufs
: Es la cantidad máxima de búferes que un canal en este puerto puede asignar de forma previa para facilitar el intercambio de datos con el cliente. Los búferes se cuentan por separado para los datos que se envían en ambas direcciones, por lo que especificar 1 aquí significaría que se preasignaron 1 búfer de envío y 1 de recepción. En general, la cantidad de búferes requeridos depende del acuerdo de protocolo de nivel superior entre el cliente y el servidor. El número puede ser tan bajo como 1 en el caso de un protocolo muy síncrono (enviar un mensaje, recibir una respuesta antes de enviar otro). Sin embargo, la cantidad puede ser mayor si el cliente espera enviar más de un mensaje antes de que pueda aparecer una respuesta (p. ej., un mensaje como prólogo y otro como el comando real). Los conjuntos de búferes asignados son por canal, por lo que dos conexiones (canales) separadas tendrían conjuntos de búferes independientes.
[in] recv_buf_size
: Es el tamaño máximo de cada búfer individual en el conjunto de búferes anterior. Este valor depende del protocolo y limita de manera eficaz el tamaño máximo de los mensajes que puedes intercambiar con el par.
[in] flags
: Es una combinación de marcas que especifica el comportamiento adicional del puerto.
Este valor debe ser una combinación de los siguientes valores:
IPC_PORT_ALLOW_TA_CONNECT
: Permite una conexión desde otras apps seguras.
IPC_PORT_ALLOW_NS_CONNECT
: Permite una conexión desde el entorno no seguro.
[retval]: Control del puerto creado si no es negativo o un error específico si es negativo
Luego, el servidor sondea la lista de controladores de puertos en busca de conexiones entrantes con la llamada wait()
. Cuando recibe una solicitud de conexión indicada por el bit IPC_HANDLE_POLL_READY
establecido en el campo event
de la estructura uevent_t
, el servidor debe llamar a accept()
para terminar de establecer una conexión y crear un canal (representado por otro identificador) que luego se pueda sondear en busca de mensajes entrantes.
accept()
Acepta una conexión entrante y obtiene un controlador para un canal.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: Control que representa el puerto al que se conectó un cliente
[out] peer_uuid
: Es un puntero a una estructura uuid_t
que se completará con el UUID de la app cliente que se conecta. Se establece en todos los ceros si la conexión se originó en el entorno no seguro.
[retval]: Control de un canal (si no es negativo) en el que el servidor puede intercambiar mensajes con el cliente (o un código de error en caso contrario)
API de cliente
Esta sección contiene los métodos de la API de cliente.
Métodos en la API de cliente
connect()
Inicia una conexión a un puerto especificado por nombre.
long connect(const char *path, uint flags);
[in] path
: Es el nombre de un puerto publicado por una app de confianza.
[in] flags
: Especifica un comportamiento adicional y opcional.
[retval]: Control de un canal a través del cual se pueden intercambiar mensajes con el servidor; error si es negativo
Si no se especifica ningún flags
(el parámetro flags
se establece en 0), llamar a connect()
inicia una conexión síncrona a un puerto especificado que muestra un error de inmediato si el puerto no existe y crea un bloqueo hasta que el servidor acepte una conexión.
Para alterar este comportamiento, especifica una combinación de dos valores, que se describen a continuación:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
: Fuerza una llamada a connect()
para que espere si el puerto especificado no existe de inmediato en la ejecución, en lugar de fallar de inmediato.
IPC_CONNECT_ASYNC
: Si se establece, inicia una conexión asíncrona. Una app debe sondear el control que se muestra llamando a wait()
para un evento de finalización de conexión indicado por el bit IPC_HANDLE_POLL_READY
establecido en el campo de evento de la estructura uevent_t
antes de iniciar el funcionamiento normal.
API de Messaging
Las llamadas a la API de Messaging permiten el envío y la lectura de mensajes a través de una conexión (canal) establecida previamente. Las llamadas a la API de Messaging son las mismas para los servidores y los clientes.
Un cliente recibe un identificador de canal mediante una llamada connect()
, y un servidor obtiene un identificador de canal de una llamada accept()
, como se describió anteriormente.
Estructura de un mensaje confiable
Como se muestra a continuación, los mensajes que intercambia la API de Trusty tienen una estructura mínima, lo que permite que el servidor y el cliente acuerden la semántica del contenido real:
/* * 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 mensaje puede estar compuesto por uno o más búferes no contiguos representados por un array de estructuras iovec_t
. Trusty realiza operaciones de lectura y escritura de dispersión y recopilación en estos bloques con el array iov
. El contenido de los búferes que puede describir el array iov
es completamente arbitrario.
Métodos de la API de Messaging
send_msg()
Envía un mensaje a través de un canal especificado.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: Control del canal a través del cual se enviará el mensaje
[in] msg
: Puntero al ipc_msg_t structure
que describe el mensaje
[retval]: Cantidad total de bytes enviados si se realiza correctamente; un error negativo de lo contrario
Si el cliente (o el servidor) intenta enviar un mensaje a través del canal y no hay espacio en la cola de mensajes del par de destino, es posible que el canal entre en un estado de bloqueo de envío (esto nunca debería suceder para un protocolo de solicitud o respuesta síncrona simple, pero puede ocurrir en casos más complicados) que se indica cuando se muestra un código de error ERR_NOT_ENOUGH_BUFFER
.
En ese caso, el llamador debe esperar hasta que el par libere espacio en su cola de recepción recuperando los mensajes de manejo y retiro, indicados por el bit IPC_HANDLE_POLL_SEND_UNBLOCKED
establecido en el campo event
de la estructura uevent_t
que muestra la llamada wait()
.
get_msg()
Obtén metainformación sobre el siguiente mensaje en una cola de mensajes entrantes.
de un canal específico.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: Es el identificador del canal en el que se debe recuperar un mensaje nuevo.
[out] msg_info
: Es la estructura de información del mensaje que se describe a continuación:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
A cada mensaje se le asigna un ID único en el conjunto de mensajes pendientes, y se completa la longitud total de cada mensaje. Si el protocolo está configurado y lo permite, puede haber varios mensajes pendientes (abiertos) a la vez para un canal en particular.
[retval]: NO_ERROR
si se realiza correctamente; un error negativo de lo contrario
read_msg()
Lee el contenido del mensaje con el ID especificado a partir del desplazamiento especificado.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: Es el identificador del canal desde el que se leerá el mensaje.
[in] msg_id
: Es el ID del mensaje que se leerá.
[in] offset
: Es el desplazamiento en el mensaje desde el que se debe comenzar a leer.
[out] msg
: Puntero a la estructura ipc_msg_t
que describe un conjunto de búferes en los que se almacenarán los datos de los mensajes entrantes.
[retval]: Cantidad total de bytes almacenados en los búferes msg
si se realiza correctamente; un error negativo en caso contrario
Se puede llamar al método read_msg
varias veces a partir de un desplazamiento diferente (no necesariamente secuencial) según sea necesario.
put_msg()
Retira un mensaje con un ID especificado.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: Es el identificador del canal al que llegó el mensaje.
[en] msg_id
: Es el ID del mensaje que se retirará.
[retval]: NO_ERROR
si se realiza correctamente; un error negativo de lo contrario
No se puede acceder al contenido de un mensaje después de que se haya retirado y se haya liberado el búfer que ocupaba.
API de File Descriptor
La API de Descriptor de archivos incluye llamadas a read()
, write()
y ioctl()
. Todas estas llamadas pueden operar en un conjunto predefinido (estático) de descriptores de archivo que, por lo general, se representan con números pequeños. En la implementación actual, el espacio del descriptor de archivo es independiente del espacio del identificador de IPC. La API de descriptor de archivos en Trusty es similar a una API tradicional basada en descriptores de archivos.
De forma predeterminada, hay 3 descriptores de archivos predefinidos (estándares y conocidos):
- 0: Entrada estándar. La implementación predeterminada de la entrada estándar
fd
es una operación no realizada (ya que no se espera que las apps de confianza tengan una consola interactiva), por lo que leer, escribir o invocarioctl()
enfd
0 debería mostrar un errorERR_NOT_SUPPORTED
. - 1: Salida estándar. Los datos escritos en el resultado estándar se pueden enrutar (según el nivel de depuración de LK) a UART o a un registro de memoria disponible en el lado no seguro, según la plataforma y la configuración. Los registros y los mensajes de depuración que no son críticos deben ir en la salida estándar. Los métodos
read()
yioctl()
son no-ops y deben mostrar un errorERR_NOT_SUPPORTED
. - 2: Error estándar. Los datos escritos en el error estándar deben enrutarse al registro de UART o memoria disponible en el lado no seguro, según la plataforma y la configuración. Se recomienda escribir solo mensajes críticos en el error estándar, ya que es muy probable que esta transmisión no se reduzca. Los métodos
read()
yioctl()
no realizan operaciones y deben mostrar un errorERR_NOT_SUPPORTED
.
Aunque este conjunto de descriptores de archivos se puede extender para implementar más fds
(para implementar extensiones específicas de la plataforma), se debe tener cuidado cuando se extienden los descriptores de archivos. Extender los descriptores de archivos suele crear conflictos y, por lo general, no se recomienda.
Métodos de la API de File Descriptor
read()
Intenta leer hasta count
bytes de datos de un descriptor de archivo especificado.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Es el descriptor de archivo desde el que se lee.
[out] buf
: Puntero a un búfer en el que se almacenarán los datos
[in] count
: Cantidad máxima de bytes que se deben leer
[retval]: Muestra la cantidad de bytes leídos; de lo contrario, muestra un error negativo.
write()
Escribe hasta count
bytes de datos en el descriptor de archivo especificado.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Es el descriptor de archivo al que se escribirá.
[out] buf
: Puntero a los datos que se escribirán
[in] count
: Cantidad máxima de bytes que se escribirán
[retval]: Muestra la cantidad de bytes escritos. De lo contrario, muestra un error negativo.
ioctl()
Invoca un comando ioctl
especificado para un descriptor de archivo determinado.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: Es el descriptor de archivos en el que se invoca ioctl()
.
[in] cmd
: El comando ioctl
[Entrada/salida] args
: Puntero a los argumentos ioctl()
API miscelánea
Métodos de la API de Miscellaneous
gettime()
Muestra la hora actual del sistema (en nanosegundos).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: Depende de la plataforma. Pasa cero para el valor predeterminado.
[in] flags
: Reservado, debe ser cero.
[out] time
: Puntero a un valor int64_t
en el que se almacenará la hora actual
[retval]: NO_ERROR
si se realiza correctamente; un error negativo de lo contrario
nanosleep()
Suspende la ejecución de la app que realiza la llamada durante un período determinado y la reanuda después de ese período.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: Reservado, debe ser cero.
[in] flags
: Reservado, debe ser cero.
[in] sleep_time
: Tiempo de suspensión en nanosegundos
[retval]: NO_ERROR
si se realiza correctamente; un error negativo de lo contrario
Ejemplo de un servidor de apps de confianza
En la siguiente app de ejemplo, se muestra el uso de las APIs anteriores. En la muestra, se crea un servicio de “eco” que controla varias conexiones entrantes y refleja al llamador todos los mensajes que recibe de los clientes que se originan desde el lado seguro o no seguro.
#include <uapi/err.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <trusty_ipc.h> #define LOG_TAG "echo_srv" #define TLOGE(fmt, ...) \ fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ##__VA_ARGS__) # define MAX_ECHO_MSG_SIZE 64 static const char * srv_name = "com.android.echo.srv.echo"; static uint8_t msg_buf[MAX_ECHO_MSG_SIZE]; /* * Message handler */ static int handle_msg(handle_t chan) { int rc; struct iovec iov; ipc_msg_t msg; ipc_msg_info_t msg_inf; iov.iov_base = msg_buf; iov.iov_len = sizeof(msg_buf); msg.num_iov = 1; msg.iov = &iov; msg.num_handles = 0; msg.handles = NULL; /* get message info */ rc = get_msg(chan, &msg_inf); if (rc == ERR_NO_MSG) return NO_ERROR; /* no new messages */ if (rc != NO_ERROR) { TLOGE("failed (%d) to get_msg for chan (%d)\n", rc, chan); return rc; } /* read msg content */ rc = read_msg(chan, msg_inf.id, 0, &msg); if (rc < 0) { TLOGE("failed (%d) to read_msg for chan (%d)\n", rc, chan); return rc; } /* update number of bytes received */ iov.iov_len = (size_t) rc; /* send message back to the caller */ rc = send_msg(chan, &msg); if (rc < 0) { TLOGE("failed (%d) to send_msg for chan (%d)\n", rc, chan); return rc; } /* retire message */ rc = put_msg(chan, msg_inf.id); if (rc != NO_ERROR) { TLOGE("failed (%d) to put_msg for chan (%d)\n", rc, chan); return rc; } return NO_ERROR; } /* * Channel event handler */ static void handle_channel_event(const uevent_t * ev) { int rc; if (ev->event & IPC_HANDLE_POLL_MSG) { rc = handle_msg(ev->handle); if (rc != NO_ERROR) { /* report an error and close channel */ TLOGE("failed (%d) to handle event on channel %d\n", rc, ev->handle); close(ev->handle); } return; } if (ev->event & IPC_HANDLE_POLL_HUP) { /* closed by peer. */ close(ev->handle); return; } } /* * Port event handler */ static void handle_port_event(const uevent_t * ev) { uuid_t peer_uuid; if ((ev->event & IPC_HANDLE_POLL_ERROR) || (ev->event & IPC_HANDLE_POLL_HUP) || (ev->event & IPC_HANDLE_POLL_MSG) || (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) { /* should never happen with port handles */ TLOGE("error event (0x%x) for port (%d)\n", ev->event, ev->handle); abort(); } if (ev->event & IPC_HANDLE_POLL_READY) { /* incoming connection: accept it */ int rc = accept(ev->handle, &peer_uuid); if (rc < 0) { TLOGE("failed (%d) to accept on port %d\n", rc, ev->handle); return; } handle_t chan = rc; while (true){ struct uevent cev; rc = wait(chan, &cev, INFINITE_TIME); if (rc < 0) { TLOGE("wait returned (%d)\n", rc); abort(); } handle_channel_event(&cev); if (cev.event & IPC_HANDLE_POLL_HUP) { return; } } } } /* * Main app entry point */ int main(void) { int rc; handle_t port; /* Initialize service */ rc = port_create(srv_name, 1, MAX_ECHO_MSG_SIZE, IPC_PORT_ALLOW_NS_CONNECT | IPC_PORT_ALLOW_TA_CONNECT); if (rc < 0) { TLOGE("Failed (%d) to create port %s\n", rc, srv_name); abort(); } port = (handle_t) rc; /* enter main event loop */ while (true) { uevent_t ev; ev.handle = INVALID_IPC_HANDLE; ev.event = 0; ev.cookie = NULL; /* wait forever */ rc = wait(port, &ev, INFINITE_TIME); if (rc == NO_ERROR) { /* got an event */ handle_port_event(&ev); } else { TLOGE("wait returned (%d)\n", rc); abort(); } } return 0; }
El método run_end_to_end_msg_test()
envía 10,000 mensajes de forma asíncrona al servicio “echo” y controla las respuestas.
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 y apps del mundo no seguro
Los programas del kernel y del espacio de usuario que se ejecutan en el lado no seguro pueden acceder a un conjunto de servicios confiables, publicados desde el lado seguro y marcados con el atributo IPC_PORT_ALLOW_NS_CONNECT
.
El entorno de ejecución del lado no seguro (kernel y espacio de usuario) es muy diferente del entorno de ejecución del lado seguro. Por lo tanto, en lugar de una sola biblioteca para ambos entornos, hay dos conjuntos diferentes de APIs. En el kernel, el controlador de kernel trusty-ipc proporciona la API cliente y registra un nodo de dispositivo de caracteres que los procesos de espacio de usuario pueden usar para comunicarse con los servicios que se ejecutan en el lado seguro.
API de cliente de IPC confiable del espacio de usuario
La biblioteca de la API cliente de IPC de Trusty en el espacio de usuario es una capa delgada sobre el nodo del dispositivo fd
.
Un programa de espacio de usuario inicia una sesión de comunicación llamando a tipc_connect()
e inicializando una conexión a un servicio de confianza especificado. De forma interna, la llamada a tipc_connect()
abre un nodo de dispositivo especificado para obtener un descriptor de archivo y, luego, invoca una llamada a TIPC_IOC_CONNECT ioctl()
con el parámetro argp
que apunta a una cadena que contiene un nombre de servicio al que conectarse.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
El descriptor de archivo resultante solo se puede usar para comunicarse con el servicio
para el que se creó. Se debe cerrar el descriptor de archivos llamando a tipc_close()
cuando ya no se requiera la conexión.
El descriptor de archivo que obtiene la llamada a tipc_connect()
se comporta como un nodo de dispositivo de caracteres típico. El descriptor de archivo hace lo siguiente:
- Se puede cambiar al modo sin bloqueo si es necesario.
- Se puede escribir con una llamada
write()
estándar para enviar mensajes al otro lado. - Se puede sondear (con llamadas
poll()
oselect()
) para verificar la disponibilidad de los mensajes entrantes como un descriptor de archivos normal. - Se puede leer para recuperar mensajes entrantes.
Un emisor envía un mensaje al servicio de Trusty mediante la ejecución de una llamada de escritura para el fd
especificado. El controlador trusty-ipc transforma en un mensaje todos los datos que se pasan a la llamada write()
anterior. El mensaje se entrega al lado seguro, donde el subsistema de IPC en el kernel de Trusty controla los datos, los enruta al destino correcto y los entrega a un bucle de eventos de la app como un evento IPC_HANDLE_POLL_MSG
en un controlador de canal en particular. Según el protocolo particular y específico del servicio, el servicio de Trusty puede enviar uno o más mensajes de respuesta que se devuelven al lado no seguro y se colocan en la cola de mensajes del descriptor de archivos de canal adecuada para que la llamada read()
de la app del espacio del usuario los recupere.
tipc_connect()
Abre un nodo de dispositivo tipc
especificado e inicia una conexión a un servicio de confianza especificado.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: Es la ruta de acceso al nodo de dispositivo IPC confiable que se abrirá.
[in] srv_name
: Es el nombre de un servicio de Trusty publicado al que conectarse.
[retval]: Es un descriptor de archivo válido si se realiza correctamente la operación; de lo contrario, es -1.
tipc_close()
Cierra la conexión al servicio de Trusty especificado por un descriptor de archivos.
int tipc_close(int fd);
[in] fd
: Es el descriptor de archivo que abrió una llamada tipc_connect()
anteriormente.
API cliente de IPC de Kernel Trusty
La API de cliente de IPC confiable del kernel está disponible para los controladores de kernel. La API de IPC de Trusty del espacio de usuario se implementa sobre esta API.
En general, el uso típico de esta API consiste en que un llamador crea un objeto struct tipc_chan
con la función tipc_create_channel()
y, luego, usa la llamada tipc_chan_connect()
para iniciar una conexión con el servicio de IPC confiable que se ejecuta en el lado seguro. Para finalizar la conexión con el extremo remoto, llama a tipc_chan_shutdown()
seguida de tipc_chan_destroy()
para limpiar los recursos.
Cuando recibe una notificación (a través de la devolución de llamada handle_event()
) de que se estableció correctamente una conexión, el llamador hace lo siguiente:
- Obtiene un búfer de mensajes con la llamada
tipc_chan_get_txbuf_timeout()
- Redacta un mensaje.
- Coloca el mensaje en cola con el método
tipc_chan_queue_msg()
para la entrega a un servicio de confianza (en el lado seguro), al que está conectado el canal.
Después de que la cola se realice correctamente, el llamador debe olvidar el búfer de mensajes, ya que este, en última instancia, regresa al grupo de búferes libres después de que el extremo remoto lo procesa (para volver a usarlo más adelante, para otros mensajes). El usuario solo necesita llamar a tipc_chan_put_txbuf()
si no puede poner en cola ese búfer o si ya no es necesario.
Un usuario de la API recibe mensajes del lado remoto controlando una devolución de llamada de notificación handle_msg()
(a la que se llama en el contexto de la lista de tareas en cola rx
de trusty-ipc) que proporciona un puntero a un búfer rx
que contiene un mensaje entrante que se debe controlar.
Se espera que la implementación de la devolución de llamada de handle_msg()
muestre un puntero a un struct tipc_msg_buf
válido.
Puede ser el mismo que el búfer de mensajes entrantes si se controla de forma local y ya no es necesario. Como alternativa, puede ser un búfer nuevo que se obtiene con una llamada a tipc_chan_get_rxbuf()
si el búfer entrante está en cola para un procesamiento adicional. Se debe hacer un seguimiento de un búfer rx
desconectado y, finalmente, liberarlo con una llamada tipc_chan_put_rxbuf()
cuando ya no sea necesario.
Métodos en la API de cliente de IPC de Kernel Trusty
tipc_create_channel()
Crea y configura una instancia de un canal de IPC confiable para un dispositivo de IPC confiable en particular.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: Puntero al trusty-ipc para el que se crea el canal del dispositivo
[in] ops
: Puntero a un struct tipc_chan_ops
, con devoluciones de llamada específicas del llamador completadas
[in] cb_arg
: Puntero a los datos que se pasan a las devoluciones de llamada de tipc_chan_ops
[retval]: Puntero a una instancia recién creada de struct tipc_chan
si se realiza correctamente, ERR_PTR(err)
de lo contrario
En general, un llamador debe proporcionar dos devoluciones de llamada que se invoquen de forma asíncrona cuando se produce la actividad correspondiente.
Se invoca el evento void (*handle_event)(void *cb_arg, int event)
para notificar a un llamador sobre un cambio de estado del canal.
[in] cb_arg
: Puntero a los datos que se pasan a una llamada tipc_create_channel()
[in] event
: Un evento que puede ser uno de los siguientes valores:
TIPC_CHANNEL_CONNECTED
: Indica que se estableció una conexión correcta con el extremo remoto.TIPC_CHANNEL_DISCONNECTED
: Indica que el extremo remoto rechazó la solicitud de conexión nueva o solicitó la desconexión del canal conectado anteriormente.TIPC_CHANNEL_SHUTDOWN
: Indica que el extremo remoto se está cerrando y que se finalizarán de forma permanente todas las conexiones.
Se invoca la devolución de llamada struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
para notificar que se recibió un mensaje nuevo a través de un canal especificado:
- [in]
cb_arg
: Puntero a los datos que se pasan a la llamadatipc_create_channel()
- [in]
mb
: Es un puntero a unstruct tipc_msg_buf
que describe un mensaje entrante. - [retval]: Se espera que la implementación de devolución de llamada devuelva un puntero a un
struct tipc_msg_buf
que puede ser el mismo puntero que se recibe como un parámetromb
si el mensaje se controla de forma local y ya no es necesario (o puede ser un búfer nuevo que obtiene la llamadatipc_chan_get_rxbuf()
).
tipc_chan_connect()
Inicia una conexión al servicio de IPC confiable especificado.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: Puntero a un canal que muestra la llamada tipc_create_chan()
[in] port
: Es un puntero a una cadena que contiene el nombre del servicio al que se debe conectar.
[retval]: 0 si se realiza correctamente, un error negativo de lo contrario
Cuando se establece una conexión, se notifica al emisor mediante una devolución de llamada handle_event
.
tipc_chan_shutdown()
Finaliza una conexión al servicio de IPC de Trusty que inició una llamada a tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: Es un puntero a un canal que muestra una llamada a tipc_create_chan()
.
tipc_chan_destroy()
Destruye un canal de IPC confiable especificado.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: Puntero a un canal que muestra la llamada tipc_create_chan()
tipc_chan_get_txbuf_timeout()
Obtiene un búfer de mensajes que se puede usar para enviar datos a través de un canal especificado. Si el búfer no está disponible de inmediato, es posible que se bloquee el llamador durante el tiempo de espera especificado (en milisegundos).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: Puntero al canal al que se debe poner en cola un mensaje
[in] chan
: Es el tiempo de espera máximo para esperar hasta que el búfer tx
esté disponible.
[retval]: Un búfer de mensajes válido si se realiza correctamente, ERR_PTR(err)
si se produce un error
tipc_chan_queue_msg()
Coloca en cola un mensaje para que se envíe a través de los canales de IPC confiables especificados.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Puntero al canal al que se debe poner en cola el mensaje
[in] mb:
Puntero al mensaje que se agregará a la cola (obtenido mediante una llamada a tipc_chan_get_txbuf_timeout()
)
[retval]: 0 si se realiza correctamente, un error negativo de lo contrario
tipc_chan_put_txbuf()
Libera el búfer de mensajes Tx
especificado que obtuvo anteriormente una llamada tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Puntero al canal al que pertenece este búfer de mensajes
[in] mb
: Puntero al búfer de mensajes que se liberará
[retval]: Ninguno
tipc_chan_get_rxbuf()
Obtiene un nuevo búfer de mensajes que se puede usar para recibir mensajes a través del canal especificado.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: Puntero a un canal al que pertenece este búfer de mensajes
[retval]: Es un búfer de mensajes válido si se realiza correctamente, ERR_PTR(err)
si se produce un error.
tipc_chan_put_rxbuf()
Libera un búfer de mensajes especificado que obtuvo anteriormente una llamada a tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Puntero a un canal al que pertenece este búfer de mensajes
[in] mb
: Puntero a un búfer de mensajes para liberar
[retval]: Ninguno