Referencia de la API de Trusty

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.

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 de send_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 invocar ioctl() en fd 0 debería mostrar un error ERR_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() y ioctl() son no-ops y deben mostrar un error ERR_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étodos ioctl() son no-ops y deberían mostrar un error ERR_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 o select() 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 remoto
  • TIPC_CHANNEL_DISCONNECTED: Indica que el lado remoto rechazó el solicitud de conexión nueva o solicitada desconexión del canal conectado anteriormente
  • TIPC_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 al tipc_create_channel() llamada
  • [in] mb: Puntero a un struct 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ámetro mb si el mensaje se maneja de forma local y no ya no se necesitan (o puede ser un búfer nuevo obtenido por la llamada tipc_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