Trusty предоставляет API для разработки двух классов приложений и сервисов:
- Доверенные приложения и службы, работающие на процессоре TEE.
- Обычные и ненадежные приложения, которые работают на главном процессоре и используют службы, предоставляемые доверенными приложениями.
Trusty API обычно описывает систему межпроцессного взаимодействия Trusty (IPC), включая связь с незащищенным миром. Программное обеспечение, работающее на главном процессоре, может использовать Trusty API для подключения к доверенным приложениям и службам и обмена с ними произвольными сообщениями, как сетевая служба по IP. Приложение должно определить формат данных и семантику этих сообщений, используя протокол уровня приложения. Надежная доставка сообщений гарантируется базовой инфраструктурой Trusty (в виде драйверов, работающих на главном процессоре), а связь является полностью асинхронной.
Порты и каналы
Порты используются приложениями Trusty для предоставления конечных точек службы в виде именованного пути, к которому подключаются клиенты. Это дает клиентам простой строковый идентификатор службы. Соглашение об именах — это именование в стиле обратного DNS, например com.google.servicename
.
Когда клиент подключается к порту, он получает канал для взаимодействия с сервисом. Служба должна принять входящее соединение, и когда это произойдет, она тоже получит канал. По сути, порты используются для поиска сервисов, а затем связь происходит по паре подключенных каналов (т. е. экземпляров соединения на порту). Когда клиент подключается к порту, устанавливается симметричное двунаправленное соединение. Используя этот полнодуплексный путь, клиенты и серверы могут обмениваться произвольными сообщениями до тех пор, пока какая-либо из сторон не решит разорвать соединение.
Только доверенные приложения защищенной стороны или модули ядра Trusty могут создавать порты. Приложения, работающие на незащищенной стороне (в обычном мире), могут подключаться только к сервисам, опубликованным на защищенной стороне.
В зависимости от требований доверенное приложение может быть одновременно клиентом и сервером. Доверенному приложению, публикующему службу (в качестве сервера), может потребоваться подключение к другим службам (в качестве клиента).
Обработка API
Дескрипторы — это целые числа без знака, представляющие такие ресурсы, как порты и каналы, подобно файловым дескрипторам в UNIX. После создания дескрипторов они помещаются в таблицу дескрипторов конкретного приложения, и на них можно ссылаться позже.
Вызывающий может связать личные данные с дескриптором, используя метод set_cookie()
.
Методы в Handle API
Дескрипторы действительны только в контексте приложения. Приложение не должно передавать значение дескриптора другим приложениям, если это явно не указано. Значение дескриптора следует интерпретировать только путем сравнения его с INVALID_IPC_HANDLE #define,
который приложение может использовать как указание на то, что дескриптор недействителен или не установлен.
set_cookie()
Связывает личные данные, предоставленные вызывающей стороной, с указанным дескриптором.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: любой дескриптор, возвращаемый одним из вызовов API.
[in] cookie
: указатель на произвольные данные пользовательского пространства в приложении Trusty.
[retval]: NO_ERROR
в случае успеха, в противном случае код ошибки < 0
.
Этот вызов полезен для обработки событий, когда они происходят позже после создания дескриптора. Механизм обработки событий передает дескриптор и его файл cookie обратно обработчику событий.
Дескрипторы событий можно ожидать с помощью вызова wait()
.
ждать()
Ожидает возникновения события на данном дескрипторе в течение указанного периода времени.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: любой дескриптор, возвращаемый одним из вызовов API.
[out] event
: указатель на структуру, представляющую событие, произошедшее в этом дескрипторе.
[in] timeout_msecs
: значение таймаута в миллисекундах; значение -1 представляет собой бесконечный тайм-аут
[retval]: NO_ERROR
, если действительное событие произошло в течение указанного интервала времени ожидания; ERR_TIMED_OUT
, если указанный тайм-аут истек, но событие не произошло; < 0
для других ошибок
В случае успеха ( retval == NO_ERROR
) вызов wait()
заполняет указанную структуру uevent_t
информацией о произошедшем событии.
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;
Поле event
содержит комбинацию следующих значений:
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
— на самом деле никаких событий не ожидается, вызывающая сторона должна перезапустить ожидание
IPC_HANDLE_POLL_ERROR
— произошла неуказанная внутренняя ошибка
IPC_HANDLE_POLL_READY
— зависит от типа дескриптора следующим образом:
- Для портов это значение указывает на наличие ожидающего соединения.
- Для каналов это значение указывает на то, что было установлено асинхронное соединение (см.
connect()
).
Следующие события актуальны только для каналов:
-
IPC_HANDLE_POLL_HUP
— указывает, что канал был закрыт партнером -
IPC_HANDLE_POLL_MSG
— указывает, что для этого канала есть ожидающее сообщение. -
IPC_HANDLE_POLL_SEND_UNBLOCKED
— указывает, что ранее заблокированный абонент может попытаться отправить сообщение еще раз (подробности см. в описанииsend_msg()
)
Обработчик событий должен быть готов обрабатывать комбинацию указанных событий, поскольку одновременно может быть установлено несколько битов. Например, для канала возможно одновременное наличие ожидающих сообщений и соединение, закрытое партнером.
Большинство событий закреплены. Они сохраняются до тех пор, пока сохраняется основное условие (например, все ожидающие сообщения получены и ожидающие запросы на соединение обрабатываются). Исключением является событие IPC_HANDLE_POLL_SEND_UNBLOCKED
, которое очищается при чтении, и у приложения есть только один шанс его обработать.
Дескрипторы можно уничтожить, вызвав метод close()
.
закрывать()
Уничтожает ресурс, связанный с указанным дескриптором, и удаляет его из таблицы дескрипторов.
long close(uint32_t handle_id);
[in] handle_id
: Дескриптор для уничтожения
[retval]: 0 в случае успеха; в противном случае отрицательная ошибка
API сервера
Сервер начинает с создания одного или нескольких именованных портов, представляющих конечные точки его службы. Каждый порт представлен дескриптором.
Методы в API сервера
порт_создать()
Создает именованный сервисный порт.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: строковое имя порта (как описано выше). Это имя должно быть уникальным во всей системе; попытки создать дубликат терпят неудачу.
[in] num_recv_bufs
: Максимальное количество буферов, которые канал на этом порту может предварительно выделить для облегчения обмена данными с клиентом. Буферы подсчитываются отдельно для данных, идущих в обоих направлениях, поэтому указание здесь 1 будет означать, что 1 буфер отправки и 1 буфер приема предварительно выделены. В общем, количество требуемых буферов зависит от соглашения протокола более высокого уровня между клиентом и сервером. Число может быть всего лишь 1 в случае очень синхронного протокола (отправка сообщения, получение ответа перед отправкой другого). Но это число может быть больше, если клиент ожидает отправить более одного сообщения, прежде чем появится ответ (например, одно сообщение в качестве пролога, а другое в качестве фактической команды). Наборы буферов выделены для каждого канала, поэтому два отдельных соединения (канала) будут иметь отдельные наборы буферов.
[in] recv_buf_size
: Максимальный размер каждого отдельного буфера в указанном выше наборе буферов. Это значение зависит от протокола и эффективно ограничивает максимальный размер сообщения, которым вы можете обмениваться с одноранговым узлом.
[in] flags
: комбинация флагов, определяющая дополнительное поведение порта.
Это значение должно представлять собой комбинацию следующих значений:
IPC_PORT_ALLOW_TA_CONNECT
— разрешает подключение из других безопасных приложений.
IPC_PORT_ALLOW_NS_CONNECT
— разрешает соединение из незащищенного мира.
[retval]: дескриптор созданного порта, если значение неотрицательное, или конкретная ошибка, если значение отрицательное.
Затем сервер опрашивает список дескрипторов портов для входящих соединений с помощью вызова wait()
. При получении запроса на соединение, обозначенного битом IPC_HANDLE_POLL_READY
, установленным в поле event
структуры uevent_t
, сервер должен вызвать accept()
чтобы завершить установление соединения и создать канал (представленный другим дескриптором), который затем можно будет опрашивать на наличие входящих сообщений. .
принимать()
Принимает входящее соединение и получает дескриптор канала.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: дескриптор, представляющий порт, к которому подключился клиент.
[out] peer_uuid
: указатель на структуру uuid_t
, которая должна быть заполнена UUID подключающегося клиентского приложения. Все значения равны нулю, если соединение исходит из незащищенного мира.
[retval]: дескриптор канала (если неотрицательный), по которому сервер может обмениваться сообщениями с клиентом (или код ошибки в противном случае).
Клиентский API
В этом разделе содержатся методы Клиентского API.
Методы в клиентском API
соединять()
Инициирует соединение с портом, указанным по имени.
long connect(const char *path, uint flags);
[in] path
: имя порта, опубликованное приложением Trusty.
[in] flags
: определяет дополнительное, необязательное поведение.
[retval]: дескриптор канала, по которому можно обмениваться сообщениями с сервером; ошибка, если отрицательный
Если flags
не указаны (параметр flags
установлен в 0), вызов метода connect()
инициирует синхронное соединение с указанным портом, который немедленно возвращает ошибку, если порт не существует, и создает блок до тех пор, пока сервер не примет иное соединение. связь.
Это поведение можно изменить, указав комбинацию двух значений, описанных ниже:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
— заставляет вызов connect()
ждать, если указанный порт не существует сразу при выполнении, вместо немедленного сбоя.
IPC_CONNECT_ASYNC
— если установлено, инициирует асинхронное соединение. Перед началом нормальной работы приложение должно запросить возвращенный дескриптор, вызвав wait()
для события завершения соединения, указанного битом IPC_HANDLE_POLL_READY
установленным в поле события структуры uevent_t
.
API обмена сообщениями
Вызовы API обмена сообщениями позволяют отправлять и читать сообщения через ранее установленное соединение (канал). Вызовы API обмена сообщениями одинаковы для серверов и клиентов.
Клиент получает дескриптор канала с помощью вызова connect()
, а сервер получает дескриптор канала с помощью вызова accept()
, описанного выше.
Структура надежного сообщения
Как показано ниже, сообщения, которыми обменивается Trusty API, имеют минимальную структуру, оставляя на усмотрение сервера и клиента согласование семантики фактического содержимого:
/* * 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;
Сообщение может состоять из одного или нескольких несмежных буферов, представленных массивом структур iovec_t
. Trusty выполняет разбросанное чтение и запись в эти блоки, используя массив iov
. Содержимое буферов, которое может быть описано массивом iov
совершенно произвольно.
Методы в API обмена сообщениями
send_msg()
Отправляет сообщение по указанному каналу.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: Дескриптор канала, по которому отправляется сообщение.
[in] msg
: Указатель на ipc_msg_t structure
описывающую сообщение.
[retval]: общее количество байтов, отправленных в случае успеха; в противном случае отрицательная ошибка
Если клиент (или сервер) пытается отправить сообщение по каналу, а в очереди сообщений узла назначения нет места, канал может перейти в состояние блокировки отправки (это никогда не должно происходить для простого синхронного протокола запроса/ответа). но может произойти в более сложных случаях), на что указывает возврат кода ошибки ERR_NOT_ENOUGH_BUFFER
. В таком случае вызывающая сторона должна дождаться, пока партнер освободит некоторое пространство в своей очереди приема, получив сообщения обработки и удаления, указанные битом IPC_HANDLE_POLL_SEND_UNBLOCKED
, установленным в поле event
структуры uevent_t
, возвращаемой вызовом wait()
.
get_msg()
Получает метаинформацию о следующем сообщении в очереди входящих сообщений.
указанного канала.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: дескриптор канала, на котором должно быть получено новое сообщение.
[out] msg_info
: структура информации о сообщении, описываемая следующим образом:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Каждому сообщению присваивается уникальный идентификатор в наборе ожидающих сообщений, и заполняется общая длина каждого сообщения. Если это настроено и разрешено протоколом, для определенного канала может быть одновременно несколько ожидающих (открытых) сообщений.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
read_msg()
Считывает содержимое сообщения с указанным идентификатором, начиная с указанного смещения.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: дескриптор канала, из которого можно прочитать сообщение.
[in] msg_id
: идентификатор сообщения для чтения.
[in] offset
: Смещение в сообщении, с которого следует начать чтение.
[out] msg
: указатель на структуру ipc_msg_t
описывающую набор буферов, в которых будут храниться данные входящего сообщения.
[retval]: общее количество байтов, сохраняемых в буферах msg
в случае успеха; в противном случае отрицательная ошибка
При необходимости метод read_msg
можно вызывать несколько раз, начиная с другого (не обязательно последовательного) смещения.
put_msg()
Удаляет сообщение с указанным идентификатором.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: дескриптор канала, на который пришло сообщение.
[in] msg_id
: идентификатор удаляемого сообщения.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
Доступ к содержимому сообщения становится невозможен после того, как сообщение было удалено и занимаемый им буфер был освобожден.
API файлового дескриптора
API файловых дескрипторов включает вызовы read()
, write()
и ioctl()
. Все эти вызовы могут работать с заранее определенным (статическим) набором файловых дескрипторов, традиционно представленных небольшими числами. В текущей реализации пространство файлового дескриптора отделено от пространства дескрипторов IPC. API файловых дескрипторов в Trusty аналогичен традиционному API на основе файловых дескрипторов.
По умолчанию существует 3 предопределённых (стандартных и общеизвестных) файловых дескриптора:
- 0 - стандартный ввод. Реализация стандартного ввода
fd
по умолчанию неактивна (поскольку доверенные приложения не должны иметь интерактивную консоль), поэтому чтение, запись или вызовioctl()
наfd
0 должны возвращать ошибкуERR_NOT_SUPPORTED
. - 1 - стандартный вывод. Данные, записываемые в стандартный вывод, могут быть направлены (в зависимости от уровня отладки LK) в UART и/или журнал памяти, доступный на незащищенной стороне, в зависимости от платформы и конфигурации. Некритические журналы отладки и сообщения должны отправляться в стандартный вывод. Методы
read()
иioctl()
являются пустыми и должны возвращать ошибкуERR_NOT_SUPPORTED
. - 2 – стандартная ошибка. Данные, записанные в стандартную ошибку, должны направляться в UART или журнал памяти, доступный на незащищенной стороне, в зависимости от платформы и конфигурации. В стандартную ошибку рекомендуется записывать только критические сообщения, так как этот поток, скорее всего, не будет регулироваться. Методы
read()
иioctl()
являются пустыми и должны возвращать ошибкуERR_NOT_SUPPORTED
.
Несмотря на то, что этот набор файловых дескрипторов можно расширить для реализации большего количества fds
(для реализации расширений, специфичных для платформы), расширение файловых дескрипторов требует осторожности. Расширение файловых дескрипторов может привести к конфликтам и обычно не рекомендуется.
Методы в API дескриптора файла
читать()
Пытается прочитать до count
байтов данных из указанного дескриптора файла.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Дескриптор файла, из которого нужно читать.
[out] buf
: Указатель на буфер, в котором будут храниться данные.
[in] count
: Максимальное количество байтов для чтения.
[retval]: возвращено количество прочитанных байтов; в противном случае отрицательная ошибка
писать()
Записывает count
байтов данных в указанный файловый дескриптор.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Дескриптор файла, в который следует писать.
[out] buf
: Указатель на данные для записи.
[in] count
: Максимальное количество байтов для записи.
[retval]: возвращено количество записанных байтов; в противном случае отрицательная ошибка
ioctl()
Вызывает указанную команду ioctl
для данного дескриптора файла.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: Дескриптор файла, для которого вызывается ioctl()
[in] cmd
: команда ioctl
[in/out] args
: Указатель на аргументы ioctl()
Разное API
Методы в Разном API
получить время()
Возвращает текущее системное время (в наносекундах).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: Зависит от платформы; передать ноль по умолчанию
[in] flags
: Зарезервировано, должно быть равно нулю.
[out] time
: указатель на значение int64_t
, в котором сохраняется текущее время.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
наносон()
Приостанавливает выполнение вызывающего приложения на указанный период времени и возобновляет его по истечении этого периода.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: зарезервировано, должно быть равно нулю.
[in] flags
: Зарезервировано, должно быть равно нулю.
[in] sleep_time
: Время сна в наносекундах.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
Пример доверенного сервера приложений
В следующем примере приложения показано использование вышеуказанных API. В примере создается служба «эха», которая обрабатывает несколько входящих подключений и возвращает вызывающей стороне все сообщения, полученные от клиентов, исходящих с защищенной или незащищенной стороны.
#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; }
Метод run_end_to_end_msg_test()
асинхронно отправляет 10 000 сообщений в службу «echo» и обрабатывает ответы.
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; }
Незащищенные API и приложения мира
Набор служб Trusty, опубликованных с защищенной стороны и отмеченных атрибутом IPC_PORT_ALLOW_NS_CONNECT
, доступен программам ядра и пользовательского пространства, работающим на незащищенной стороне.
Среда выполнения на незащищенной стороне (ядро и пользовательское пространство) радикально отличается от среды выполнения на защищенной стороне. Таким образом, вместо одной библиотеки для обеих сред существует два разных набора API. В ядре клиентский API предоставляется драйвером ядра Trusty-IPC и регистрирует узел символьного устройства, который может использоваться процессами пользовательского пространства для связи со службами, работающими на безопасной стороне.
Пользовательское пространство Trusty IPC Client API
Библиотека API-интерфейса Trusty IPC Client пространства пользователя представляет собой тонкий слой поверх узла устройства fd
.
Программа пользовательского пространства запускает сеанс связи, вызывая tipc_connect()
, инициализируя соединение с указанной службой Trusty. Внутренний вызов tipc_connect()
открывает указанный узел устройства для получения дескриптора файла и вызывает вызов TIPC_IOC_CONNECT ioctl()
с параметром argp
указывающим на строку, содержащую имя службы, к которой необходимо подключиться.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Полученный дескриптор файла можно использовать только для связи со службой, для которой он был создан. Дескриптор файла следует закрыть, вызвав tipc_close()
когда соединение больше не требуется.
Дескриптор файла, полученный вызовом tipc_connect()
ведет себя как типичный узел символьного устройства; дескриптор файла:
- При необходимости можно переключиться в неблокирующий режим.
- Может быть записан с использованием стандартного вызова
write()
для отправки сообщений другой стороне. - Может быть опрошен (с использованием вызовов
poll()
или вызововselect()
) на наличие входящих сообщений в виде обычного файлового дескриптора. - Может быть прочитано для получения входящих сообщений
Вызывающая сторона отправляет сообщение в службу Trusty, выполняя вызов записи для указанного fd
. Все данные, переданные вышеприведенному вызову write()
преобразуются в сообщение драйвером Trusty-IPC. Сообщение доставляется на безопасную сторону, где данные обрабатываются подсистемой IPC в ядре Trusty, направляются в нужное место назначения и доставляются в цикл событий приложения как событие IPC_HANDLE_POLL_MSG
в определенном дескрипторе канала. В зависимости от конкретного протокола, специфичного для службы, служба Trusty может отправлять одно или несколько ответных сообщений, которые доставляются обратно на незащищенную сторону и помещаются в соответствующую очередь сообщений дескриптора файла канала, которая может быть получена приложением пользовательского пространства read()
вызов.
Tipc_connect()
Открывает указанный узел устройства tipc
и инициирует подключение к указанной доверенной службе.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: Путь к узлу устройства Trusty IPC, который нужно открыть.
[in] srv_name
: Имя опубликованной службы Trusty, к которой необходимо подключиться.
[retval]: действительный дескриптор файла в случае успеха, в противном случае -1.
типк_закрыть ()
Закрывает соединение со службой Trusty, указанной в файловом дескрипторе.
int tipc_close(int fd);
[in] fd
: Дескриптор файла, ранее открытый вызовом tipc_connect()
Клиентский API Kernel Trusty IPC
Клиентский API Trusty IPC ядра доступен для драйверов ядра. API Trusty IPC пользовательского пространства реализован поверх этого API.
В общем, типичное использование этого API заключается в том, что вызывающая сторона создает объект struct tipc_chan
с помощью функции tipc_create_channel()
, а затем использует вызов tipc_chan_connect()
для инициации соединения со службой Trusty IPC, работающей на безопасной стороне. Соединение с удаленной стороной можно прервать, вызвав tipc_chan_shutdown()
за которым следует tipc_chan_destroy()
для очистки ресурсов.
Получив уведомление (через обратный вызов handle_event()
) об успешном установлении соединения, вызывающая сторона делает следующее:
- Получает буфер сообщений с помощью вызова
tipc_chan_get_txbuf_timeout()
- Составляет сообщение и
- Ставит сообщение в очередь с помощью метода
tipc_chan_queue_msg()
для доставки в службу Trusty (на защищенной стороне), к которой подключен канал.
После успешной постановки в очередь вызывающая сторона должна забыть буфер сообщений, поскольку буфер сообщений в конечном итоге возвращается в свободный пул буферов после обработки удаленной стороной (для повторного использования позже для других сообщений). Пользователю нужно вызвать tipc_chan_put_txbuf()
только в том случае, если ему не удалось поставить такой буфер в очередь или он больше не требуется.
Пользователь API получает сообщения с удаленной стороны, обрабатывая обратный вызов уведомления handle_msg()
(который вызывается в контексте рабочей очереди Trusty-IPC rx
), который предоставляет указатель на буфер rx
содержащий входящее сообщение, которое необходимо обработать.
Ожидается, что реализация обратного вызова handle_msg()
вернет указатель на действительную struct tipc_msg_buf
. Это может быть то же самое, что и буфер входящих сообщений, если он обрабатывается локально и больше не требуется. Альтернативно это может быть новый буфер, полученный вызовом tipc_chan_get_rxbuf()
, если входящий буфер поставлен в очередь для дальнейшей обработки. Отсоединенный буфер rx
необходимо отслеживать и в конечном итоге освобождать с помощью вызова tipc_chan_put_rxbuf()
когда он больше не нужен.
Методы клиентского API Kernel Trusty IPC
Tipc_create_channel()
Создает и настраивает экземпляр канала Trusty IPC для конкретного устройства Trusty-IPC.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: указатель на Trusty-IPC, для которого создан канал устройства.
[in] ops
: указатель на struct tipc_chan_ops
с заполненными обратными вызовами для конкретного вызывающего абонента.
[in] cb_arg
: Указатель на данные, которые передаются в обратные вызовы tipc_chan_ops
.
[retval]: указатель на вновь созданный экземпляр struct tipc_chan
в случае успеха, ERR_PTR(err)
в противном случае.
Как правило, вызывающая сторона должна предоставить два обратных вызова, которые вызываются асинхронно при выполнении соответствующего действия.
Событие void (*handle_event)(void *cb_arg, int event)
вызывается для уведомления вызывающей стороны об изменении состояния канала.
[in] cb_arg
: Указатель на данные, передаваемые в вызов tipc_create_channel()
[in] event
: событие, которое может иметь одно из следующих значений:
-
TIPC_CHANNEL_CONNECTED
— указывает на успешное подключение к удаленной стороне -
TIPC_CHANNEL_DISCONNECTED
— указывает, что удаленная сторона отклонила новый запрос на подключение или запросила отключение ранее подключенного канала. -
TIPC_CHANNEL_SHUTDOWN
— указывает, что удаленная сторона отключается, окончательно прекращая все соединения.
Обратный вызов struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
вызывается для уведомления о том, что новое сообщение получено по указанному каналу:
- [in]
cb_arg
: Указатель на данные, передаваемые в вызовtipc_create_channel()
- [in]
mb
: Указатель наstruct tipc_msg_buf
описывающую входящее сообщение. - [retval]: ожидается, что реализация обратного вызова вернет указатель на
struct tipc_msg_buf
, который может быть тем же указателем, полученным в качестве параметраmb
, если сообщение обрабатывается локально и больше не требуется (или это может быть новый буфер, полученный вызовtipc_chan_get_rxbuf()
)
Tipc_chan_connect()
Инициирует подключение к указанной службе Trusty IPC.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: указатель на канал, возвращаемый вызовом tipc_create_chan()
.
[in] port
: указатель на строку, содержащую имя службы, к которой необходимо подключиться.
[retval]: 0 в случае успеха, в противном случае — отрицательная ошибка.
Вызывающий уведомляется об установлении соединения путем получения обратного вызова handle_event
.
Tipc_chan_shutdown()
Завершает соединение со службой Trusty IPC, ранее инициированное вызовом tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: Указатель на канал, возвращаемый вызовом tipc_create_chan()
.
Tipc_chan_destroy()
Уничтожает указанный канал Trusty IPC.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: указатель на канал, возвращаемый вызовом tipc_create_chan()
.
Tipc_chan_get_txbuf_timeout()
Получает буфер сообщений, который можно использовать для отправки данных по указанному каналу. Если буфер недоступен немедленно, вызывающий абонент может быть заблокирован на указанный тайм-аут (в миллисекундах).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: Указатель на канал, в который следует поставить сообщение в очередь.
[in] chan
: Максимальное время ожидания, пока буфер tx
не станет доступным.
[retval]: действительный буфер сообщений в случае успеха, ERR_PTR(err)
в случае ошибки.
Tipc_chan_queue_msg()
Ставит сообщение в очередь для отправки по указанным каналам Trusty IPC.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Указатель на канал, в который следует поставить сообщение в очередь.
[in] mb:
Указатель на сообщение в очереди (полученный вызовом tipc_chan_get_txbuf_timeout()
)
[retval]: 0 в случае успеха, в противном случае — отрицательная ошибка.
Tipc_chan_put_txbuf()
Освобождает указанный буфер сообщения Tx
, ранее полученный вызовом tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Указатель на канал, которому принадлежит этот буфер сообщений.
[in] mb
: Указатель на буфер сообщений, который нужно освободить.
[ретвал]: Нет
Tipc_chan_get_rxbuf()
Получает новый буфер сообщений, который можно использовать для приема сообщений по указанному каналу.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: Указатель на канал, которому принадлежит этот буфер сообщений.
[retval]: действительный буфер сообщений в случае успеха, ERR_PTR(err)
в случае ошибки.
Tipc_chan_put_rxbuf()
Освобождает указанный буфер сообщений, ранее полученный вызовом tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Указатель на канал, которому принадлежит этот буфер сообщений.
[in] mb
: Указатель на буфер сообщений, который нужно освободить.
[ретвал]: Нет
,Trusty предоставляет API для разработки двух классов приложений и сервисов:
- Доверенные приложения и службы, работающие на процессоре TEE.
- Обычные и ненадежные приложения, которые работают на главном процессоре и используют службы, предоставляемые доверенными приложениями.
Trusty API обычно описывает систему межпроцессного взаимодействия Trusty (IPC), включая связь с незащищенным миром. Программное обеспечение, работающее на главном процессоре, может использовать Trusty API для подключения к доверенным приложениям и службам и обмена с ними произвольными сообщениями, как сетевая служба по IP. Приложение должно определить формат данных и семантику этих сообщений, используя протокол уровня приложения. Надежная доставка сообщений гарантируется базовой инфраструктурой Trusty (в виде драйверов, работающих на главном процессоре), а связь является полностью асинхронной.
Порты и каналы
Порты используются приложениями Trusty для предоставления конечных точек службы в виде именованного пути, к которому подключаются клиенты. Это дает клиентам простой строковый идентификатор службы. Соглашение об именах — это именование в стиле обратного DNS, например com.google.servicename
.
Когда клиент подключается к порту, он получает канал для взаимодействия с сервисом. Служба должна принять входящее соединение, и когда это произойдет, она тоже получит канал. По сути, порты используются для поиска сервисов, а затем связь происходит по паре подключенных каналов (т. е. экземпляров соединения на порту). Когда клиент подключается к порту, устанавливается симметричное двунаправленное соединение. Используя этот полнодуплексный путь, клиенты и серверы могут обмениваться произвольными сообщениями до тех пор, пока какая-либо из сторон не решит разорвать соединение.
Только доверенные приложения защищенной стороны или модули ядра Trusty могут создавать порты. Приложения, работающие на незащищенной стороне (в обычном мире), могут подключаться только к сервисам, опубликованным на защищенной стороне.
В зависимости от требований доверенное приложение может быть одновременно клиентом и сервером. Доверенному приложению, публикующему службу (в качестве сервера), может потребоваться подключение к другим службам (в качестве клиента).
Обработка API
Дескрипторы — это целые числа без знака, представляющие такие ресурсы, как порты и каналы, подобно файловым дескрипторам в UNIX. После создания дескрипторов они помещаются в таблицу дескрипторов конкретного приложения, и на них можно ссылаться позже.
Вызывающий может связать личные данные с дескриптором, используя метод set_cookie()
.
Методы в Handle API
Дескрипторы действительны только в контексте приложения. Приложение не должно передавать значение дескриптора другим приложениям, если это явно не указано. Значение дескриптора следует интерпретировать только путем сравнения его с INVALID_IPC_HANDLE #define,
который приложение может использовать как указание на то, что дескриптор недействителен или не установлен.
set_cookie()
Связывает личные данные, предоставленные вызывающей стороной, с указанным дескриптором.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: любой дескриптор, возвращаемый одним из вызовов API.
[in] cookie
: указатель на произвольные данные пользовательского пространства в приложении Trusty.
[retval]: NO_ERROR
в случае успеха, в противном случае код ошибки < 0
.
Этот вызов полезен для обработки событий, когда они происходят позже после создания дескриптора. Механизм обработки событий передает дескриптор и его файл cookie обратно обработчику событий.
Дескрипторы событий можно ожидать с помощью вызова wait()
.
ждать()
Ожидает возникновения события на данном дескрипторе в течение указанного периода времени.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: любой дескриптор, возвращаемый одним из вызовов API.
[out] event
: указатель на структуру, представляющую событие, произошедшее в этом дескрипторе.
[in] timeout_msecs
: значение таймаута в миллисекундах; значение -1 представляет собой бесконечный тайм-аут
[retval]: NO_ERROR
, если действительное событие произошло в течение указанного интервала времени ожидания; ERR_TIMED_OUT
, если указанный тайм-аут истек, но событие не произошло; < 0
для других ошибок
В случае успеха ( retval == NO_ERROR
) вызов wait()
заполняет указанную структуру uevent_t
информацией о произошедшем событии.
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;
Поле event
содержит комбинацию следующих значений:
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
— на самом деле никаких событий не ожидается, вызывающая сторона должна перезапустить ожидание
IPC_HANDLE_POLL_ERROR
— произошла неуказанная внутренняя ошибка
IPC_HANDLE_POLL_READY
— зависит от типа дескриптора следующим образом:
- Для портов это значение указывает на наличие ожидающего соединения.
- Для каналов это значение указывает на то, что было установлено асинхронное соединение (см.
connect()
).
Следующие события актуальны только для каналов:
-
IPC_HANDLE_POLL_HUP
— указывает, что канал был закрыт партнером -
IPC_HANDLE_POLL_MSG
— указывает, что для этого канала есть ожидающее сообщение. -
IPC_HANDLE_POLL_SEND_UNBLOCKED
— указывает, что ранее заблокированный абонент может попытаться отправить сообщение еще раз (подробности см. в описанииsend_msg()
)
Обработчик событий должен быть готов обрабатывать комбинацию указанных событий, поскольку одновременно может быть установлено несколько битов. Например, для канала возможно одновременное наличие ожидающих сообщений и соединение, закрытое партнером.
Большинство событий закреплены. Они сохраняются до тех пор, пока сохраняется основное условие (например, все ожидающие сообщения получены и ожидающие запросы на соединение обрабатываются). Исключением является событие IPC_HANDLE_POLL_SEND_UNBLOCKED
, которое очищается при чтении, и у приложения есть только один шанс его обработать.
Дескрипторы можно уничтожить, вызвав метод close()
.
закрывать()
Уничтожает ресурс, связанный с указанным дескриптором, и удаляет его из таблицы дескрипторов.
long close(uint32_t handle_id);
[in] handle_id
: Дескриптор для уничтожения
[retval]: 0 в случае успеха; в противном случае отрицательная ошибка
API сервера
Сервер начинает с создания одного или нескольких именованных портов, представляющих конечные точки его службы. Каждый порт представлен дескриптором.
Методы в API сервера
порт_создать()
Создает именованный сервисный порт.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: строковое имя порта (как описано выше). Это имя должно быть уникальным во всей системе; попытки создать дубликат терпят неудачу.
[in] num_recv_bufs
: Максимальное количество буферов, которые канал на этом порту может предварительно выделить для облегчения обмена данными с клиентом. Буферы подсчитываются отдельно для данных, идущих в обоих направлениях, поэтому указание здесь 1 будет означать, что 1 буфер отправки и 1 буфер приема предварительно выделены. В общем, количество требуемых буферов зависит от соглашения протокола более высокого уровня между клиентом и сервером. Число может быть всего лишь 1 в случае очень синхронного протокола (отправка сообщения, получение ответа перед отправкой другого). Но это число может быть больше, если клиент ожидает отправить более одного сообщения, прежде чем появится ответ (например, одно сообщение в качестве пролога, а другое в качестве фактической команды). Наборы буферов выделены для каждого канала, поэтому два отдельных соединения (канала) будут иметь отдельные наборы буферов.
[in] recv_buf_size
: Максимальный размер каждого отдельного буфера в указанном выше наборе буферов. Это значение зависит от протокола и эффективно ограничивает максимальный размер сообщения, которым вы можете обмениваться с одноранговым узлом.
[in] flags
: комбинация флагов, определяющая дополнительное поведение порта.
Это значение должно представлять собой комбинацию следующих значений:
IPC_PORT_ALLOW_TA_CONNECT
— разрешает подключение из других безопасных приложений.
IPC_PORT_ALLOW_NS_CONNECT
— разрешает соединение из незащищенного мира.
[retval]: дескриптор созданного порта, если значение неотрицательное, или конкретная ошибка, если значение отрицательное.
Затем сервер опрашивает список дескрипторов портов для входящих соединений с помощью вызова wait()
. При получении запроса на соединение, обозначенного битом IPC_HANDLE_POLL_READY
, установленным в поле event
структуры uevent_t
, сервер должен вызвать accept()
чтобы завершить установление соединения и создать канал (представленный другим дескриптором), который затем можно будет опрашивать на наличие входящих сообщений. .
принимать()
Принимает входящее соединение и получает дескриптор канала.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: дескриптор, представляющий порт, к которому подключился клиент.
[out] peer_uuid
: указатель на структуру uuid_t
, которая должна быть заполнена UUID подключающегося клиентского приложения. Все значения равны нулю, если соединение исходит из незащищенного мира.
[retval]: дескриптор канала (если неотрицательный), по которому сервер может обмениваться сообщениями с клиентом (или код ошибки в противном случае).
Клиентский API
В этом разделе содержатся методы Клиентского API.
Методы в клиентском API
соединять()
Инициирует соединение с портом, указанным по имени.
long connect(const char *path, uint flags);
[in] path
: имя порта, опубликованное приложением Trusty.
[in] flags
: определяет дополнительное, необязательное поведение.
[retval]: дескриптор канала, по которому можно обмениваться сообщениями с сервером; ошибка, если отрицательный
Если flags
не указаны (параметр flags
установлен в 0), вызов метода connect()
инициирует синхронное соединение с указанным портом, который немедленно возвращает ошибку, если порт не существует, и создает блок до тех пор, пока сервер не примет иное соединение. связь.
Это поведение можно изменить, указав комбинацию двух значений, описанных ниже:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
— заставляет вызов connect()
ждать, если указанный порт не существует сразу при выполнении, вместо немедленного сбоя.
IPC_CONNECT_ASYNC
— если установлено, инициирует асинхронное соединение. Перед началом нормальной работы приложение должно запросить возвращенный дескриптор, вызвав wait()
для события завершения соединения, указанного битом IPC_HANDLE_POLL_READY
установленным в поле события структуры uevent_t
.
API обмена сообщениями
Вызовы API обмена сообщениями позволяют отправлять и читать сообщения через ранее установленное соединение (канал). Вызовы API обмена сообщениями одинаковы для серверов и клиентов.
Клиент получает дескриптор канала с помощью вызова connect()
, а сервер получает дескриптор канала с помощью вызова accept()
, описанного выше.
Структура надежного сообщения
Как показано ниже, сообщения, которыми обменивается Trusty API, имеют минимальную структуру, оставляя на усмотрение сервера и клиента согласование семантики фактического содержимого:
/* * 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;
Сообщение может состоять из одного или нескольких несмежных буферов, представленных массивом структур iovec_t
. Trusty выполняет разбросанное чтение и запись в эти блоки, используя массив iov
. Содержимое буферов, которое может быть описано массивом iov
совершенно произвольно.
Методы в API обмена сообщениями
send_msg()
Отправляет сообщение по указанному каналу.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: Дескриптор канала, по которому отправляется сообщение.
[in] msg
: Указатель на ipc_msg_t structure
описывающую сообщение.
[retval]: общее количество байтов, отправленных в случае успеха; в противном случае отрицательная ошибка
Если клиент (или сервер) пытается отправить сообщение по каналу, а в очереди сообщений узла назначения нет места, канал может перейти в состояние блокировки отправки (это никогда не должно происходить для простого синхронного протокола запроса/ответа). но может произойти в более сложных случаях), на что указывает возврат кода ошибки ERR_NOT_ENOUGH_BUFFER
. В таком случае вызывающая сторона должна дождаться, пока партнер освободит некоторое пространство в своей очереди приема, получив сообщения обработки и удаления, указанные битом IPC_HANDLE_POLL_SEND_UNBLOCKED
, установленным в поле event
структуры uevent_t
, возвращаемой вызовом wait()
.
get_msg()
Получает метаинформацию о следующем сообщении в очереди входящих сообщений.
указанного канала.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: дескриптор канала, на котором должно быть получено новое сообщение.
[out] msg_info
: структура информации о сообщении, описываемая следующим образом:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Каждому сообщению присваивается уникальный идентификатор в наборе ожидающих сообщений, и заполняется общая длина каждого сообщения. Если это настроено и разрешено протоколом, для определенного канала может быть одновременно несколько ожидающих (открытых) сообщений.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
read_msg()
Считывает содержимое сообщения с указанным идентификатором, начиная с указанного смещения.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: дескриптор канала, из которого можно прочитать сообщение.
[in] msg_id
: идентификатор сообщения для чтения.
[in] offset
: Смещение в сообщении, с которого следует начать чтение.
[out] msg
: указатель на структуру ipc_msg_t
описывающую набор буферов, в которых будут храниться данные входящего сообщения.
[retval]: общее количество байтов, сохраняемых в буферах msg
в случае успеха; в противном случае отрицательная ошибка
При необходимости метод read_msg
можно вызывать несколько раз, начиная с другого (не обязательно последовательного) смещения.
put_msg()
Удаляет сообщение с указанным идентификатором.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: дескриптор канала, на который пришло сообщение.
[in] msg_id
: идентификатор удаляемого сообщения.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
Доступ к содержимому сообщения становится невозможен после того, как сообщение было удалено и занимаемый им буфер был освобожден.
API файлового дескриптора
API файловых дескрипторов включает вызовы read()
, write()
и ioctl()
. Все эти вызовы могут работать с заранее определенным (статическим) набором файловых дескрипторов, традиционно представленных небольшими числами. В текущей реализации пространство файлового дескриптора отделено от пространства дескрипторов IPC. API файловых дескрипторов в Trusty аналогичен традиционному API на основе файловых дескрипторов.
По умолчанию существует 3 предопределённых (стандартных и общеизвестных) файловых дескриптора:
- 0 - стандартный ввод. Реализация стандартного ввода
fd
по умолчанию неактивна (поскольку доверенные приложения не должны иметь интерактивную консоль), поэтому чтение, запись или вызовioctl()
наfd
0 должны возвращать ошибкуERR_NOT_SUPPORTED
. - 1 - стандартный вывод. Данные, записываемые в стандартный вывод, могут быть направлены (в зависимости от уровня отладки LK) в UART и/или журнал памяти, доступный на незащищенной стороне, в зависимости от платформы и конфигурации. Некритические журналы отладки и сообщения должны отправляться в стандартный вывод. Методы
read()
иioctl()
являются пустыми и должны возвращать ошибкуERR_NOT_SUPPORTED
. - 2 – стандартная ошибка. Данные, записанные в стандартную ошибку, должны направляться в UART или журнал памяти, доступный на незащищенной стороне, в зависимости от платформы и конфигурации. В стандартную ошибку рекомендуется записывать только критические сообщения, так как этот поток, скорее всего, не будет регулироваться. Методы
read()
иioctl()
являются пустыми и должны возвращать ошибкуERR_NOT_SUPPORTED
.
Несмотря на то, что этот набор файловых дескрипторов можно расширить для реализации большего количества fds
(для реализации расширений, специфичных для платформы), расширение файловых дескрипторов требует осторожности. Расширение файловых дескрипторов может привести к конфликтам и обычно не рекомендуется.
Методы в API дескриптора файла
читать()
Пытается прочитать до count
байтов данных из указанного дескриптора файла.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Дескриптор файла, из которого нужно читать.
[out] buf
: Указатель на буфер, в котором будут храниться данные.
[in] count
: Максимальное количество байтов для чтения.
[retval]: возвращено количество прочитанных байтов; в противном случае отрицательная ошибка
писать()
Записывает count
байтов данных в указанный файловый дескриптор.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: Дескриптор файла, в который следует писать.
[out] buf
: Указатель на данные для записи.
[in] count
: Максимальное количество байтов для записи.
[retval]: возвращено количество записанных байтов; в противном случае отрицательная ошибка
ioctl()
Вызывает указанную команду ioctl
для данного дескриптора файла.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: Дескриптор файла, для которого вызывается ioctl()
[in] cmd
: команда ioctl
[in/out] args
: Указатель на аргументы ioctl()
Разное API
Методы в Разном API
получить время()
Возвращает текущее системное время (в наносекундах).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: Зависит от платформы; передать ноль по умолчанию
[in] flags
: Зарезервировано, должно быть равно нулю.
[out] time
: указатель на значение int64_t
, в котором сохраняется текущее время.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
наносон()
Приостанавливает выполнение вызывающего приложения на указанный период времени и возобновляет его по истечении этого периода.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: зарезервировано, должно быть равно нулю.
[in] flags
: Зарезервировано, должно быть равно нулю.
[in] sleep_time
: Время сна в наносекундах.
[retval]: NO_ERROR
в случае успеха; в противном случае отрицательная ошибка
Пример доверенного сервера приложений
В следующем примере приложения показано использование вышеуказанных API. В примере создается служба «эха», которая обрабатывает несколько входящих подключений и возвращает вызывающей стороне все сообщения, полученные от клиентов, исходящих с защищенной или незащищенной стороны.
#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; }
Метод run_end_to_end_msg_test()
асинхронно отправляет 10 000 сообщений в службу «echo» и обрабатывает ответы.
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; }
Незащищенные API и приложения мира
Набор служб Trusty, опубликованных с защищенной стороны и отмеченных атрибутом IPC_PORT_ALLOW_NS_CONNECT
, доступен программам ядра и пользовательского пространства, работающим на незащищенной стороне.
Среда выполнения на незащищенной стороне (ядро и пользовательское пространство) радикально отличается от среды выполнения на защищенной стороне. Таким образом, вместо одной библиотеки для обеих сред существует два разных набора API. В ядре клиентский API предоставляется драйвером ядра Trusty-IPC и регистрирует узел символьного устройства, который может использоваться процессами пользовательского пространства для связи со службами, работающими на безопасной стороне.
Пользовательское пространство Trusty IPC Client API
Библиотека API-интерфейса Trusty IPC Client пространства пользователя представляет собой тонкий слой поверх узла устройства fd
.
Программа пользовательского пространства запускает сеанс связи, вызывая tipc_connect()
, инициализируя соединение с указанной службой Trusty. Внутренний вызов tipc_connect()
открывает указанный узел устройства для получения дескриптора файла и вызывает вызов TIPC_IOC_CONNECT ioctl()
с параметром argp
указывающим на строку, содержащую имя службы, к которой необходимо подключиться.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Полученный дескриптор файла можно использовать только для связи со службой, для которой он был создан. Дескриптор файла следует закрыть, вызвав tipc_close()
когда соединение больше не требуется.
Дескриптор файла, полученный вызовом tipc_connect()
ведет себя как типичный узел символьного устройства; дескриптор файла:
- При необходимости можно переключиться в неблокирующий режим.
- Может быть записан с использованием стандартного вызова
write()
для отправки сообщений другой стороне. - Может быть опрошен (с использованием вызовов
poll()
или вызововselect()
) на наличие входящих сообщений в виде обычного файлового дескриптора. - Может быть прочитано для получения входящих сообщений
Вызывающая сторона отправляет сообщение в службу Trusty, выполняя вызов записи для указанного fd
. Все данные, переданные вышеприведенному вызову write()
преобразуются в сообщение драйвером Trusty-IPC. Сообщение доставляется на безопасную сторону, где данные обрабатываются подсистемой IPC в ядре Trusty, направляются в нужное место назначения и доставляются в цикл событий приложения как событие IPC_HANDLE_POLL_MSG
в определенном дескрипторе канала. В зависимости от конкретного протокола, специфичного для службы, служба Trusty может отправлять одно или несколько ответных сообщений, которые доставляются обратно на незащищенную сторону и помещаются в соответствующую очередь сообщений дескриптора файла канала, которая может быть получена приложением пользовательского пространства read()
вызов.
Tipc_connect()
Открывает указанный узел устройства tipc
и инициирует подключение к указанной доверенной службе.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: Путь к узлу устройства Trusty IPC, который нужно открыть.
[in] srv_name
: Имя опубликованной службы Trusty, к которой необходимо подключиться.
[retval]: действительный дескриптор файла в случае успеха, в противном случае -1.
типк_закрыть ()
Закрывает соединение со службой Trusty, указанной в файловом дескрипторе.
int tipc_close(int fd);
[in] fd
: Дескриптор файла, ранее открытый вызовом tipc_connect()
Клиентский API Kernel Trusty IPC
Клиентский API Trusty IPC ядра доступен для драйверов ядра. API Trusty IPC пользовательского пространства реализован поверх этого API.
В общем, типичное использование этого API заключается в том, что вызывающая сторона создает объект struct tipc_chan
с помощью функции tipc_create_channel()
, а затем использует вызов tipc_chan_connect()
для инициации соединения со службой Trusty IPC, работающей на безопасной стороне. Соединение с удаленной стороной можно прервать, вызвав tipc_chan_shutdown()
за которым следует tipc_chan_destroy()
для очистки ресурсов.
Получив уведомление (через обратный вызов handle_event()
) об успешном установлении соединения, вызывающая сторона делает следующее:
- Получает буфер сообщений с помощью вызова
tipc_chan_get_txbuf_timeout()
- Составляет сообщение и
- Ставит сообщение в очередь с помощью метода
tipc_chan_queue_msg()
для доставки в службу Trusty (на защищенной стороне), к которой подключен канал.
После успешной постановки в очередь вызывающая сторона должна забыть буфер сообщений, поскольку буфер сообщений в конечном итоге возвращается в свободный пул буферов после обработки удаленной стороной (для повторного использования позже для других сообщений). Пользователю нужно вызвать tipc_chan_put_txbuf()
только в том случае, если ему не удалось поставить такой буфер в очередь или он больше не требуется.
Пользователь API получает сообщения с удаленной стороны, обрабатывая обратный вызов уведомления handle_msg()
(который вызывается в контексте рабочей очереди Trusty-IPC rx
), который предоставляет указатель на буфер rx
содержащий входящее сообщение, которое необходимо обработать.
Ожидается, что реализация обратного вызова handle_msg()
вернет указатель на действительную struct tipc_msg_buf
. Это может быть то же самое, что и буфер входящих сообщений, если он обрабатывается локально и больше не требуется. Альтернативно это может быть новый буфер, полученный вызовом tipc_chan_get_rxbuf()
, если входящий буфер поставлен в очередь для дальнейшей обработки. Отсоединенный буфер rx
необходимо отслеживать и в конечном итоге освобождать с помощью вызова tipc_chan_put_rxbuf()
когда он больше не нужен.
Методы клиентского API Kernel Trusty IPC
Tipc_create_channel()
Создает и настраивает экземпляр канала Trusty IPC для конкретного устройства Trusty-IPC.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: указатель на Trusty-IPC, для которого создан канал устройства.
[in] ops
: указатель на struct tipc_chan_ops
с заполненными обратными вызовами для конкретного вызывающего абонента.
[in] cb_arg
: Указатель на данные, которые передаются в обратные вызовы tipc_chan_ops
.
[retval]: указатель на вновь созданный экземпляр struct tipc_chan
в случае успеха, ERR_PTR(err)
в противном случае.
Как правило, вызывающая сторона должна предоставить два обратных вызова, которые вызываются асинхронно при выполнении соответствующего действия.
Событие void (*handle_event)(void *cb_arg, int event)
вызывается для уведомления вызывающей стороны об изменении состояния канала.
[in] cb_arg
: Указатель на данные, передаваемые в вызов tipc_create_channel()
[in] event
: событие, которое может иметь одно из следующих значений:
-
TIPC_CHANNEL_CONNECTED
— указывает на успешное подключение к удаленной стороне -
TIPC_CHANNEL_DISCONNECTED
— указывает, что удаленная сторона отклонила новый запрос на подключение или запросила отключение ранее подключенного канала. -
TIPC_CHANNEL_SHUTDOWN
— указывает, что удаленная сторона отключается, окончательно прекращая все соединения.
Обратный вызов struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
вызывается для уведомления о том, что новое сообщение получено по указанному каналу:
- [in]
cb_arg
: Указатель на данные, передаваемые в вызовtipc_create_channel()
- [in]
mb
: Указатель наstruct tipc_msg_buf
описывающую входящее сообщение. - [retval]: ожидается, что реализация обратного вызова вернет указатель на
struct tipc_msg_buf
, который может быть тем же указателем, полученным в качестве параметраmb
, если сообщение обрабатывается локально и больше не требуется (или это может быть новый буфер, полученный вызовtipc_chan_get_rxbuf()
)
Tipc_chan_connect()
Инициирует подключение к указанной службе Trusty IPC.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: указатель на канал, возвращаемый вызовом tipc_create_chan()
.
[in] port
: указатель на строку, содержащую имя службы, к которой необходимо подключиться.
[retval]: 0 в случае успеха, в противном случае — отрицательная ошибка.
Вызывающий уведомляется об установлении соединения путем получения обратного вызова handle_event
.
Tipc_chan_shutdown()
Завершает соединение со службой Trusty IPC, ранее инициированное вызовом tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: Указатель на канал, возвращаемый вызовом tipc_create_chan()
.
Tipc_chan_destroy()
Уничтожает указанный канал Trusty IPC.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: указатель на канал, возвращаемый вызовом tipc_create_chan()
.
Tipc_chan_get_txbuf_timeout()
Получает буфер сообщений, который можно использовать для отправки данных по указанному каналу. Если буфер недоступен немедленно, вызывающий абонент может быть заблокирован на указанный тайм-аут (в миллисекундах).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: Указатель на канал, в который следует поставить сообщение в очередь.
[in] chan
: Максимальное время ожидания, пока буфер tx
не станет доступным.
[retval]: действительный буфер сообщений в случае успеха, ERR_PTR(err)
в случае ошибки.
Tipc_chan_queue_msg()
Ставит сообщение в очередь для отправки по указанным каналам Trusty IPC.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Указатель на канал, в который следует поставить сообщение в очередь.
[in] mb:
Указатель на сообщение в очереди (полученный вызовом tipc_chan_get_txbuf_timeout()
)
[retval]: 0 в случае успеха, в противном случае — отрицательная ошибка.
Tipc_chan_put_txbuf()
Освобождает указанный буфер сообщения Tx
, ранее полученный вызовом tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Указатель на канал, которому принадлежит этот буфер сообщений.
[in] mb
: Указатель на буфер сообщений, который нужно освободить.
[ретвал]: Нет
Tipc_chan_get_rxbuf()
Получает новый буфер сообщений, который можно использовать для приема сообщений по указанному каналу.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: Указатель на канал, которому принадлежит этот буфер сообщений.
[retval]: действительный буфер сообщений в случае успеха, ERR_PTR(err)
в случае ошибки.
Tipc_chan_put_rxbuf()
Освобождает указанный буфер сообщений, ранее полученный вызовом tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Указатель на канал, которому принадлежит этот буфер сообщений.
[in] mb
: Указатель на буфер сообщений, который нужно освободить.
[ретвал]: Нет