Trusty proporciona APIs para desarrollar dos clases de aplicaciones y servicios:
- Aplicaciones o servicios de confianza que se ejecutan en el procesador TEE
- Aplicaciones normales o no confiables que se ejecutan en el procesador principal y usan los servicios proporcionados por aplicaciones de confianza
El Fideicomiso La API 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 El encargado del tratamiento de datos principal puede usar APIs de Trusty para conectarse a aplicaciones o servicios de confianza. y también intercambiar mensajes arbitrarios con ellos, como con un servicio de red por IP. Depende de la aplicación determinar el formato de datos y la semántica de estos con un protocolo a nivel de la app. La entrega confiable de mensajes es garantizadas por la infraestructura subyacente de Trusty (en forma de controladores que se ejecuta en el procesador principal) y la comunicación es completamente asíncrona.
Puertos y canales
Las aplicaciones Trusty usan puertos para exponer los extremos del servicio de la siguiente forma:
de una ruta con nombre a la que se conectan los clientes. Esto brinda una interfaz simple, basada en cadenas
el ID de servicio que pueden usar los clientes. La convención de nomenclatura es de estilo DNS inverso
de nombres, 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 cuándo recibe, también recibe un canal. En términos simples, los puertos se usan para buscar servicios Luego, la comunicación se produce 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, un clúster simétrico, una conexión bidireccional. Con esta ruta de acceso dúplex, los clientes y servidores pueden intercambiar mensajes arbitrarios hasta que cualquiera de los lados decida romper la conexión.
Solo las aplicaciones de confianza del lado seguro o los módulos de kernel de Trusty pueden crear puertos. Las aplicaciones que se ejecutan en el lado no seguro (en el mundo normal) pueden solo se conectarán a servicios publicados por el lado seguro.
Según los requisitos, una aplicación de confianza puede ser cliente y servidor al mismo tiempo. Una aplicación de confianza que publica un servicio (como de un servidor) deban conectarse a otros servicios (como cliente).
API de Handle
Los identificadores son números enteros sin firma que representan recursos como puertos y similares a los descriptores de archivos de UNIX. Después de que se crean los identificadores, se colocan en una tabla de identificadores específica de la aplicación y se puede hacer referencia a ellos más adelante.
Un llamador puede asociar datos privados con un identificador usando
el método set_cookie()
Métodos en la API de Handle
Los identificadores solo son válidos en el contexto de una aplicación. Una aplicación debe
no deben pasar el valor de un controlador a otras aplicaciones, a menos que se indique explícitamente
especificada. Un valor de identificador solo debe interpretarse comparándolo con
el objeto INVALID_IPC_HANDLE #define,
que puede usar una aplicación como
indicación de que un identificador no es válido o no está configurado.
set_cookie()
Asocia los datos privados proporcionados por el llamador con un controlador especificado.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: Cualquier controlador que muestre una de las llamadas a la API
[in] cookie
: Es el puntero a datos arbitrarios del espacio del usuario en la aplicación Trusty.
[retval]: NO_ERROR
si se realiza correctamente, de lo contrario, < 0
código de error
Esta llamada es útil para controlar eventos que ocurren más adelante después de se creó el identificador. El mecanismo de control de eventos proporciona el controlador y sus cookies de vuelta al controlador de eventos.
Para los eventos, se puede esperar que los identificadores se usen con la llamada a wait()
.
wait()
Espera a que ocurra un evento en un controlador determinado durante un período específico.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: Cualquier controlador que muestre una de las llamadas a la API
[out] event
: Un puntero a la estructura que representa
un evento que ocurrió en este controlador
[in] timeout_msecs
: Un valor de tiempo de espera en milisegundos. pañal
el valor de -1 es un tiempo de espera infinito
[retval]: NO_ERROR
si se produce un evento válido en un
intervalo de tiempo de espera especificado; ERR_TIMED_OUT
si transcurrió un tiempo de espera especificado, pero no
se produjo un error. < 0
para otros errores
Si se realiza de manera correcta (retval == NO_ERROR
), la llamada a wait()
llena una estructura de uevent_t
especificada con información sobre
el evento que ocurrió.
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 realmente,
el llamador debe reiniciar el tiempo de espera
IPC_HANDLE_POLL_ERROR
: Se produjo un error interno no especificado
IPC_HANDLE_POLL_READY
: Depende del tipo de controlador, como se indica a continuación:
- Para los puertos, este valor indica que hay una conexión pendiente.
- Para los canales, este valor indica que una conexión asíncrona
(consulta
connect()
) se estableció.
Los siguientes eventos solo son relevantes para los canales:
IPC_HANDLE_POLL_HUP
: Indica que un colega cerró el canal.IPC_HANDLE_POLL_MSG
: Indica que hay un mensaje pendiente para este canal.IPC_HANDLE_POLL_SEND_UNBLOCKED
: Indica que un operador emisor bloqueado puede intentar enviar un mensaje otra vez (consulta la descripción desend_msg()
para obtener más detalles)
Un controlador de eventos debe estar preparado para manejar una combinación de ya que se pueden configurar varios bits al mismo tiempo. Por ejemplo, para un canal, es posible tener mensajes pendientes y una conexión cerrada por un pares al mismo tiempo.
La mayoría de los eventos son fijos. Persisten mientras la condición subyacente
Persiste (por ejemplo, se recibieron todos los mensajes pendientes y si la conexión está pendiente)
cómo manejar las solicitudes de servicio). La excepción es el caso de
el evento IPC_HANDLE_POLL_SEND_UNBLOCKED
, que
se aprueba luego de una lectura y la aplicación solo tiene una oportunidad
para manejarlo.
Los identificadores se pueden destruir llamando al método close()
.
cerrar()
Destruye el recurso asociado con el controlador especificado y lo quita del la tabla de controladores.
long close(uint32_t handle_id);
[in] handle_id
: Control para destruir
[retval]: 0 si tiene éxito; un error negativo; de lo contrario,
API del servidor
Un servidor comienza con la creación de uno o más puertos con nombre que representan los puntos de acceso de su servicio. Cada puerto se representa con un controlador.
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 string del puerto (como se describió antes). Esta
el nombre debe ser único en todo el sistema; los intentos de crear un duplicado fallarán.
[in] num_recv_bufs
: La cantidad máxima de búferes en los que un canal
este puerto puede asignarse previamente para facilitar el intercambio de datos con el cliente. Se cuentan los búferes
por separado para los datos que van en ambas direcciones, por lo que especificar 1 aquí significaría 1
de envío y 1 de recepción. En general, la cantidad de búferes
requerido depende del acuerdo de protocolo de nivel superior entre el cliente y
servidor. El número puede ser tan pequeño como 1 en el caso de un protocolo muy síncrono
(envía un mensaje, recibe una respuesta antes de enviar otro). Pero el número puede ser
más si el cliente espera enviar más de un mensaje antes de que pueda obtener
(p. ej., un mensaje como prólogo y otro como comando real). El
los conjuntos de búferes asignados son por canal, de modo que dos conexiones separadas (canales)
tendría conjuntos de búferes separados.
[in] recv_buf_size
: Tamaño máximo de cada búfer individual en la
por encima del conjunto de búferes. Este valor es de
depende del protocolo y limita eficazmente el tamaño máximo del mensaje que se puede intercambiar.
con intercambio de tráfico
[in] flags
: 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 aplicaciones seguras.
IPC_PORT_ALLOW_NS_CONNECT
: Permite una conexión desde el mundo no seguro.
[retval]: Controlador del puerto creado si no es negativo o un error específico si negativa
Luego, el servidor sondea la lista de controladores de puertos para detectar conexiones entrantes.
mediante la llamada wait()
Luego de recibir una conexión
indicada por el bit IPC_HANDLE_POLL_READY
establecido en
el campo event
de la estructura uevent_t
, el
el servidor debe llamar a accept()
para terminar de establecer una conexión y crear una
canal (representado por
otro controlador) que luego se pueden consultar si hay 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
: Controlador que representa el puerto al que se conectó un cliente
[out] peer_uuid
: Es el puntero a una estructura uuid_t
que se va a tratar.
completado con el UUID de la aplicación cliente que se conecta. Integra
se establecerá en ceros si la conexión se originó en un entorno no seguro
[retval]: Controlador para un canal (si no es negativo) en el que el servidor pueda intercambiar mensajes con el cliente (o, de lo contrario, un código de error)
API del cliente
Esta sección contiene los métodos en la API del cliente.
Métodos en la API del cliente
connect()
Inicia una conexión con un puerto especificado con su nombre.
long connect(const char *path, uint flags);
[en] path
: Nombre de un puerto publicado por una aplicación de Trusty
[in] flags
: Especifica el comportamiento adicional y opcional
[retval]: Controlador para un canal a través del cual se pueden intercambiar mensajes con el servidor; error si es negativo
Si no se especifica un flags
(el parámetro flags
)
está configurado en 0), llamar a connect()
inicia una conexión síncrona
a un puerto específico que inmediatamente
devuelve un error si el puerto no existe y crea un bloque hasta que
de lo contrario acepta una conexión.
Este comportamiento puede modificarse especificando una combinación de dos valores, como se describe a continuación:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
: Fuerza un 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 configura, inicia una conexión asíncrona. Los
la aplicación debe sondear
el controlador que se muestra (llamando a wait()
para
un evento de finalización de conexión indicado por el objeto IPC_HANDLE_POLL_READY
bit establecido en el campo de evento de la estructura uevent_t
antes de comenzar
el funcionamiento normal.
API de Messaging
Las llamadas a la API de mensajería permiten el envío y la lectura de mensajes a través de un una conexión establecida con anterioridad (canal). Las llamadas a la API de Messaging son lo mismo para servidores y clientes.
Un cliente recibe un identificador de un canal mediante la emisión de un connect()
.
y un servidor obtiene un controlador de canal
a partir de una llamada a accept()
,
descrita anteriormente.
Estructura de un mensaje de Trusty
Como se muestra a continuación, los mensajes intercambiados por la API de Trusty tienen una cantidad mínima de de la organización, dejando que el servidor y el cliente acuerden la semántica del Contenidos reales:
/* * 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 un método de recolección
lee y escribe en estos bloques
con el array iov
. El contenido de los búferes que se puede describir
por el array iov
es completamente arbitrario.
Métodos en 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
: Identificador del canal a través del que se envía el mensaje
[in] msg
: Puntero del ipc_msg_t structure
que describe el mensaje
[retval]: Cantidad total de bytes enviados en caso de éxito; un error negativo; de lo contrario,
Si el cliente (o servidor) intenta enviar un mensaje a través del canal y
no hay espacio en la cola de mensajes del par de destino, el canal
pasan a un estado de bloqueo de envío (esto nunca debería ocurrir
protocolo de solicitud/respuesta, pero puede ocurrir en casos más complicados) que es
se indica con un código de error ERR_NOT_ENOUGH_BUFFER
.
En ese caso, el llamador debe esperar hasta que el par libere algunas
en su cola de recepción recuperando los mensajes de manejo y eliminación,
indicado por el bit IPC_HANDLE_POLL_SEND_UNBLOCKED
establecido en
el campo event
de la estructura uevent_t
que muestra la llamada a wait()
.
get_msg().
Obtiene información meta 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 controlador del canal en el que se debe recuperar un mensaje nuevo.
[out] msg_info
: Estructura de la información del mensaje de la siguiente manera:
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 entre el conjunto de mensajes pendientes y se completa la longitud total de cada mensaje. Si la configura y permite puede haber varios mensajes pendientes (abiertos) a la vez en un canal en particular.
[retval]: NO_ERROR
si la operación se realizó correctamente; un error negativo; de lo contrario,
read_msg()
Lee el contenido del mensaje con el ID especificado comenzando desde el el 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 lee el mensaje.
[in] msg_id
: ID del mensaje que se leerá
[in] offset
: Desplazamiento hacia el mensaje desde el que se empieza 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 mensajes entrantes
datos
[retval]: Cantidad total de bytes almacenados en los búferes msg
en
éxito; un error negativo; de lo contrario,
Se puede llamar al método read_msg
varias veces a partir del
es 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.
[in] msg_id
: ID del mensaje que se retirará
[retval]: NO_ERROR
si la operación se realizó correctamente; un error negativo; de lo contrario,
No se puede acceder al contenido del mensaje después de que este se haya retirado y el el búfer que ocupaba se liberó.
API de File Descriptor
La API de File Descriptor incluye read()
, write()
,
y ioctl()
. Todas estas llamadas pueden operar en un conjunto predefinido (estático) de archivos
descriptores tradicionalmente representados con números pequeños. En la
implementación, el espacio del descriptor de archivo es independiente del controlador de IPC
espacio. La API de File Descriptor en Trusty es
similar a una API tradicional basada en descriptores de archivos.
De forma predeterminada, hay 3 descriptores de archivo predefinidos (estándar y conocidos):
- 0: entrada estándar. La implementación predeterminada de
fd
de entrada estándar es una no-op (ya que no se espera que las aplicaciones de confianza tengan un console) por lo que puedes leer, escribir o invocarioctl()
enfd
0 debería mostrar un errorERR_NOT_SUPPORTED
. - 1: salida estándar. Los datos escritos en la salida estándar se pueden enrutar
en el nivel de depuración LK) a UART o a un registro de memoria disponible en la red
según la plataforma y la configuración. Los registros de depuración no críticos y
los mensajes deben ir en la salida estándar.
read()
yioctl()
son no-ops y deben mostrar un errorERR_NOT_SUPPORTED
. - 2: Error estándar. Los datos escritos en el error estándar se deben enrutar a la UART
o registro de memoria disponible por el lado no seguro, según la plataforma y
configuración. Se recomienda escribir solo los mensajes importantes en estándar
ya que es muy probable que no se limite esta transmisión. Los operadores
read()
y Los métodosioctl()
son no-ops y deberían 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), que extiende las necesidades de descriptores de archivos
debe ejercerse con precaución. La extensión de los descriptores de archivos suele crearse
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
: Descriptor de archivos desde el que se lee
[out] buf
: Es un puntero a un búfer en el que se almacenarán los datos.
[in] count
: Cantidad máxima de bytes para leer
[retval]: Cantidad de bytes leídos que se muestran; un error negativo; de lo contrario,
write()
Escribe hasta count
bytes de datos en el descriptor de archivos especificado.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Descriptor de archivos en el que se escribirá
[out] buf
: Puntero de datos que se escribirán
[in] count
: Cantidad máxima de bytes para escribir
[retval]: Cantidad de bytes escritos que se muestra; un error negativo; de lo contrario,
ioctl()
Invoca un comando ioctl
especificado para un descriptor de archivos determinado.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: Descriptor de archivos en el que se invocará ioctl()
[in] cmd
: El comando ioctl
[entrada/salida] args
: puntero a ioctl()
argumentos
API varias
Métodos en 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; pase cero para la configuración predeterminada
[in] flags
: Reservado, debe ser cero
[out] time
: puntero a un valor int64_t
para almacenar la hora actual.
[retval]: NO_ERROR
si la operación se realizó correctamente; un error negativo; de lo contrario,
nanosleep()
Suspende la ejecución de la aplicación que realiza la llamada durante un período específico y lo 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 la operación se realizó correctamente; un error negativo; de lo contrario,
Ejemplo de un servidor de aplicaciones confiable
El siguiente ejemplo de aplicación muestra el uso de las API anteriores. La muestra crea un “eco”. que controla múltiples conexiones entrantes y Le refleja al emisor todos los mensajes que recibe de los clientes que se originaron. de la parte segura o no segura.
#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; }
El método run_end_to_end_msg_test()
envía 10,000 mensajes de forma asíncrona.
al "eco" y los controladores
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 aplicaciones no seguras en el mundo
Un conjunto de servicios de Trusty, publicados desde el lado seguro y marcados con
el atributo IPC_PORT_ALLOW_NS_CONNECT
, son accesibles para el kernel
y los programas de espacio de usuario que se ejecutan
y no seguro.
El entorno de ejecución en el lado no seguro (kernel y espacio de usuario) es muy diferente al entorno de ejecución en el lado seguro. Por lo tanto, en lugar de tener una sola biblioteca para ambos entornos, hay dos diferentes conjuntos de APIs. En el kernel, la API del cliente es proporcionada por el Trustedy-ipc y registra un nodo de dispositivo de caracteres que se puede usar. por procesos de espacio de usuario para comunicarse con los servicios que se ejecutan lado derecho.
API de cliente de Trusty IPC de espacio de usuario
La biblioteca de la API cliente Trusty IPC Client del espacio de usuario es una capa delgada sobre
nodo del dispositivo fd
.
Un programa de espacio de usuario inicia una sesión de comunicación
llamando a tipc_connect()
lo que inicializa una conexión
a un servicio de Trusty especificado. Internamente,
la llamada tipc_connect()
abre un nodo de dispositivo especificado para
obtén un descriptor de archivo e invoca un TIPC_IOC_CONNECT ioctl()
.
con el parámetro argp
que apunta a una cadena que contiene un
el nombre del servicio al cual 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 la que se creó. El descriptor de archivo debe cerrarse
llamando a tipc_close()
cuando ya no se requiere la conexión.
El descriptor de archivo obtenido por la llamada tipc_connect()
se comporta como un nodo de dispositivo de caracteres típicos; el descriptor del archivo:
- Si es necesario, se puede cambiar al modo sin bloqueo.
- Se puede escribir con un
write()
estándar. para enviar mensajes a la otra persona - Se puede sondear (mediante
poll()
llamadas oselect()
llamadas) para la disponibilidad de mensajes entrantes como descriptor de archivos normal - Se pueden leer para recuperar mensajes entrantes.
El emisor envía un mensaje al servicio Trusty a través de la ejecución de una llamada de escritura para
el fd
especificado. Se pasaron todos los datos a la llamada write()
anterior.
se transforma en un mensaje con el controlador trusty-ipc. El mensaje es
al lado seguro, donde el subsistema de IPC
controla los datos
kernel de Trusty, se enruta al destino adecuado y se entrega a una app
bucle de evento como un evento IPC_HANDLE_POLL_MSG
en un canal en particular
o un controlador de políticas. Según el modelo
protocolo específico del servicio, el servicio Trusty puede enviar una o más respuestas
mensajes que se devuelven al lado no seguro y se colocan en
cola de mensajes del descriptor de archivos de canal adecuado que el usuario recuperará
llamada read()
de la aplicación del espacio.
tipc_connect()
Abre un nodo del dispositivo tipc
especificado y, luego, inicia un
a un servicio Trusty especificado.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: Ruta de acceso al nodo del dispositivo IPC de Trusty que se debe abrir
[in] srv_name
: Nombre de un servicio de Trusty publicado al que te conectarás
[retval]: Descriptor de archivo válido en caso de éxito; de lo contrario, es -1.
tipc_close()
Cierra la conexión con el servicio Trusty que especifica un descriptor de archivos.
int tipc_close(int fd);
[en] fd
: Descriptor de archivos abierto anteriormente por
una llamada a tipc_connect()
API de Kernel Trusty IPC Client
La API del cliente Trusty IPC del kernel está disponible para los controladores de kernel. El usuario La API de Trusty IPC se implementa sobre esta API.
En general, el uso típico
de esta API consiste en que un llamador
un objeto struct tipc_chan
con tipc_create_channel()
y, luego, usa la llamada tipc_chan_connect()
para iniciar un
conexión al servicio Trusty IPC que se ejecuta en el
lado derecho. La conexión con el lado remoto puede finalizarse
llamando a tipc_chan_shutdown()
seguido de
tipc_chan_destroy()
para limpiar los recursos.
Después de recibir una notificación (a través de la devolución de llamada handle_event()
)
que una conexión se estableció correctamente, el emisor
lo siguiente:
- Obtiene un búfer de mensajes mediante la llamada a
tipc_chan_get_txbuf_timeout()
. - Redacta un mensaje
- Pone en cola el mensaje con
tipc_chan_queue_msg()
. método de entrega a un servicio Trusty (en el lado seguro), al que se el canal está conectado
Una vez que se ponga en cola de forma correcta, el llamador debería olvidar el búfer de mensajes.
debido a que el búfer de mensajes finalmente vuelve al grupo libre del búfer después
por el lado remoto (para reutilizarlo más tarde, para otros mensajes). El usuario
solo necesita llamar a tipc_chan_put_txbuf()
si no puede
pon en cola dicho búfer o ya no será necesario.
Un usuario de API recibe mensajes del lado remoto manejando un
Devolución de llamada de notificación handle_msg()
(que se llama en
el contexto de la cola de trabajo de rx
de trusty-ipc)
proporciona un puntero para un búfer rx
que contiene un
mensaje entrante que se manejará.
Se espera que la devolución de llamada handle_msg()
la implementación mostrará un puntero para un struct tipc_msg_buf
válido.
Puede ser el mismo que el búfer del mensaje entrante si se maneja de forma local.
y ya no son necesarias. Como alternativa, puede ser un búfer nuevo obtenido por
Una llamada a tipc_chan_get_rxbuf()
si el búfer entrante está en cola
para su procesamiento posterior. Se debe realizar un seguimiento de un búfer rx
desconectado
y, finalmente, lo liberan con una llamada a tipc_chan_put_rxbuf()
cuando
ya no es necesario.
Métodos en la API del cliente de IPC de Kernel Trusty
tipc_create_channel()
Crea y configura una instancia de un canal IPC de Trusty para un determinado canal. Trustedy-ipc.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: puntero a la trusty-ipc para la que se creó el dispositivo
se crea el canal
[in] ops
: puntero a un struct tipc_chan_ops
,
con requisitos específicos del emisor
devoluciones de llamada completadas
[in] cb_arg
: Puntero de datos que se pasarán
a las devoluciones de llamada de tipc_chan_ops
[retval]: puntero a una instancia recién creada de
struct tipc_chan
si la operación es correcta,
ERR_PTR(err)
por lo demás
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 emisor sobre un cambio en el estado del canal.
[in] cb_arg
: Es el puntero a los datos que se pasaron a un
tipc_create_channel()
llamada
[in] event
: Un evento que puede ser uno de los siguientes valores:
TIPC_CHANNEL_CONNECTED
: Indica una conexión exitosa. al lado remotoTIPC_CHANNEL_DISCONNECTED
: Indica que el lado remoto rechazó el solicitud de conexión nueva o solicitada desconexión del canal conectado anteriormenteTIPC_CHANNEL_SHUTDOWN
: Indica que el control remoto se está apagando. finalizan permanentemente todas las conexiones
El struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
se invoca la devolución de llamada para notificar que se ha recibido un mensaje nuevo
recibidos a través de un canal específico:
- [in]
cb_arg
: Es el puntero a los datos que se pasan altipc_create_channel()
llamada - [in]
mb
: Puntero a unstruct tipc_msg_buf
describir 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 pueden ser el mismo puntero recibido como El parámetromb
si el mensaje se maneja de forma local y no ya no se necesitan (o puede ser un búfer nuevo obtenido por la llamadatipc_chan_get_rxbuf()
)
tipc_chan_connect()
Inicia una conexión al servicio Trusty IPC de confianza.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: puntero a un canal que muestra el
tipc_create_chan()
llamada
[in] port
: Es el puntero a una cadena que contiene el
el nombre del servicio al cual conectarse
[retval]: 0 si se realiza correctamente; de lo contrario, un error negativo
El emisor recibe una notificación cuando se establece una conexión mediante la recepción de un
handle_event
.
tipc_chan_shutdown()
Finaliza una conexión al servicio Trusty IPC iniciado anteriormente
por una llamada tipc_chan_connect()
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: Puntero a un canal que devuelve un
una llamada a tipc_create_chan()
tipc_chan_destroy()
Destruye un canal IPC de Trusty especificado.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: puntero a un canal que muestra el
tipc_create_chan()
llamada
tipc_chan_get_txbuf_timeout()
Obtiene un búfer de mensaje que puede utilizarse para enviar datos a través de un canal. Si el búfer no está disponible de inmediato, es posible que se bloquee el emisor 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 hacia el canal al que se debe poner en cola un mensaje.
[in] chan
: Tiempo de espera máximo para esperar hasta la
Se vuelve disponible el búfer tx
[retval]: Un búfer de mensajes válido en caso de éxito.
ERR_PTR(err)
por error
tipc_chan_queue_msg().
Pone en cola un mensaje que se enviará Canales IPC confiables.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: puntero hacia el canal al que se debe poner en cola el mensaje.
[in] mb:
Puntero del mensaje en cola
(se obtiene de una llamada a tipc_chan_get_txbuf_timeout()
)
[retval]: 0 si se realiza correctamente; de lo contrario, un error negativo
tipc_chan_put_txbuf()
Libera el búfer de mensajes Tx
especificado
que se obtuvo previamente mediante una llamada a tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: puntero hacia el canal al que
este búfer de mensajes pertenece
[in] mb
: Es el puntero al búfer de mensajes que se liberará.
[retval]: Ninguno
tipc_chan_get_rxbuf()
Obtiene un nuevo búfer de mensajes que puede utilizarse para recibir mensajes a través del canal especificado.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: Es un puntero a un canal al que pertenece este búfer de mensajes.
[retval]: Un búfer de mensajes válido si se ejecuta de forma correcta y ERR_PTR(err)
si se produce un error
tipc_chan_put_rxbuf()
Libera un búfer de mensajes especificado obtenido previamente por un
tipc_chan_get_rxbuf()
llamada.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Es un puntero a un canal al que pertenece este búfer de mensajes.
[in] mb
: Es el puntero a un búfer de mensajes para liberarlo.
[retval]: Ninguno