Trusty udostępnia interfejsy API do tworzenia 2 klas aplikacji i usług:
- Zaufane aplikacje i usługi działające na procesorze TEE
- zwykłe i niezaufane aplikacje działające na głównym procesorze i korzystające z usług oferowanych przez zaufane aplikacje;
Interfejs API Trusty ogólnie opisuje system komunikacji międzyprocesowej (IPC) Trusty, w tym komunikację z niepewnym środowiskiem. Oprogramowanie działające na głównym procesorze może używać interfejsów Trusty API do łączenia się z zaufanymi aplikacjami i usługami oraz do wymiany dowolnych wiadomości z tymi aplikacjami i usługami, tak jak w przypadku usługi sieciowej przez protokół IP. Format danych i semantykę tych wiadomości określa aplikacja za pomocą protokołu na poziomie aplikacji. niezawodność przesyłania wiadomości jest gwarantowana przez podstawową infrastrukturę Trusty (w postaci sterowników działających na głównym procesorze), a komunikacja jest całkowicie asynchroniczna.
Porty i kanały
Porty są używane przez zaufane aplikacje do udostępniania punktów końcowych usługi w postaci nazwanej ścieżki, z którą łączą się klienci. Dzięki temu klienci mogą używać prostego identyfikatora usługi opartego na ciągu znaków. Konwencja nazewnictwa jest zgodna ze stylem odwrotnego DNS, np. com.google.servicename
.
Gdy klient łączy się z portem, otrzymuje kanał do interakcji z usługą. Usługa musi akceptować połączenia przychodzące. Gdy to zrobi, również otrzyma kanał. Zasadniczo porty służą do wyszukiwania usług, a następnie komunikacja odbywa się za pomocą pary połączonych kanałów (np. instancji połączenia na porcie). Gdy klient łączy się z portem, nawiązywane jest symetryczne, dwukierunkowe połączenie. Dzięki tej ścieżce pełnego dwukierunkowego przesyłania danych klienci i serwery mogą wymieniać dowolne wiadomości, dopóki jedna ze stron nie zdecyduje się na zerwanie połączenia.
Tylko zaufane aplikacje po stronie zabezpieczeń lub moduły jądra Trusty mogą tworzyć porty. Aplikacje działające po stronie niezabezpieczonej (w normalnym świecie) mogą łączyć się tylko z usługami opublikowanymi po stronie zabezpieczonej.
W zależności od wymagań zaufana aplikacja może być jednocześnie klientem i serwerem. Zaufana aplikacja, która publikuje usługę (jako serwer), może potrzebować połączenia z innymi usługami (jako klient).
Obsługa interfejsu API
Identyfikatory to bez znaku liczby całkowite reprezentujące zasoby, takie jak porty i kanały, podobne do deskryptorów plików w systemie UNIX. Po utworzeniu uchwyty są umieszczane w specyficznej dla aplikacji tabeli uchwytów i można je później wykorzystać.
Dzwoniący może powiązać dane prywatne z identyfikatorem za pomocą metody set_cookie()
.
Metody w interfejsie Handle API
Uchwyty są ważne tylko w kontekście aplikacji. Aplikacja nie powinna przekazywać wartości uchwytu innym aplikacjom, chyba że jest to wyraźnie określone. Wartość uchwytu powinna być interpretowana tylko przez porównanie z wartością INVALID_IPC_HANDLE #define,
, którą aplikacja może użyć jako wskazanie, że uchwyt jest nieprawidłowy lub nie ustawiony.
set_cookie()
Powiązanie danych prywatnych przekazanych przez dzwoniącego z określonym nickiem.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: dowolny identyfikator zwrócony przez jedno z wywołań interfejsu API
[in] cookie
: wskaźnik do dowolnych danych w przestrzeni użytkownika w aplikacji Trusty
[retval]: NO_ERROR
w przypadku powodzenia, w przeciwnym razie kod błędu < 0
To wywołanie jest przydatne do obsługi zdarzeń, które występują po utworzeniu uchwytu. Mechanizm obsługi zdarzeń przekazuje uchwyt i ciasteczko z powrotem do modułu obsługi zdarzeń.
Za pomocą wywołania wait()
można oczekiwać na uchwyty w przypadku zdarzeń.
wait()
Czeka na wystąpienie zdarzenia w przypadku danego uchwytu przez określony czas.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: dowolny identyfikator zwrócony przez jedno z wywołań interfejsu API
[out] event
: wskaźnik do struktury reprezentującej zdarzenie, które miało miejsce w ramach tego identyfikatora
[in] timeout_msecs
: wartość limitu czasu w milisekundach; wartość -1 oznacza nieskończony limit czasu.
[retval]: NO_ERROR
, jeśli w okresie określonego limitu czasu wystąpiło prawidłowe zdarzenie; ERR_TIMED_OUT
, jeśli upłynął określony limit czasu, ale nie wystąpiło żadne zdarzenie; < 0
w przypadku innych błędów.
W przypadku powodzenia (retval == NO_ERROR
) wywołanie wait()
wypełnia określoną strukturę uevent_t
informacjami o wydarzeniach, które miały miejsce.
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;
Pole event
zawiera kombinację tych wartości:
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
– żadne zdarzenia nie oczekują,
wywołujący powinien ponownie rozpocząć oczekiwanie
IPC_HANDLE_POLL_ERROR
– wystąpił nieokreślony błąd wewnętrzny
IPC_HANDLE_POLL_READY
– zależy od typu uchwytu:
- W przypadku portów ta wartość wskazuje, że połączenie jest oczekujące
- W przypadku kanałów ta wartość wskazuje, że zostało utworzone połączenie asynchroniczne (zob.
connect()
).
Te zdarzenia dotyczą tylko kanałów:
IPC_HANDLE_POLL_HUP
– oznacza, że kanał został zamknięty przez inną osobęIPC_HANDLE_POLL_MSG
– oznacza, że na tym kanale jest oczekująca wiadomośćIPC_HANDLE_POLL_SEND_UNBLOCKED
– oznacza, że osoba, która została wcześniej zablokowana, może ponownie próbować wysłać wiadomość (szczegóły znajdziesz w opisiesend_msg()
)
Obsługa zdarzenia powinna być przygotowana na obsługę kombinacji określonych zdarzeń, ponieważ wiele bitów może być ustawionych jednocześnie. Na przykład w przypadku kanału można mieć oczekujące wiadomości i połączenie zamknięte przez peera w tym samym czasie.
Większość zdarzeń jest trwała. Pozostają one aktywne tak długo, jak długo występuje warunek, który je wywołał (np. do czasu, gdy wszystkie oczekujące wiadomości zostaną odebrane i oczekujące żądania połączenia zostaną przetworzone). Wyjątkiem jest zdarzenie IPC_HANDLE_POLL_SEND_UNBLOCKED
, które jest wyczyszczane po odczycie, a aplikacja ma tylko jedną szansę na jego obsłużenie.
Uchwyty można zniszczyć, wywołując metodę close()
.
close()
Niszczy zasób powiązany z określonym identyfikatorem i usuwa go z tabeli identyfikatorów.
long close(uint32_t handle_id);
[in] handle_id
: Handle to destroy
[retval]: 0, jeśli operacja się powiodła, w przeciwnym razie błąd o wartości ujemnej.
Server API
Serwer zaczyna od utworzenia co najmniej jednego portu o nazwie reprezentującego punkty końcowe usługi. Każdy port jest reprezentowany przez uchwyt.
Metody w interfejsie Server API
port_create()
Tworzy port usługi o nazwie.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: ciąg tekstowy zawierający nazwę portu (jak opisano powyżej). Ta nazwa powinna być niepowtarzalna w całym systemie. Próby utworzenia duplikatu nie powiedzą się.
[in] num_recv_bufs
: maksymalna liczba buforów, które kanał na tym porcie może zarezerwować z wyprzedzeniem, aby ułatwić wymianę danych z klientem. Bufory są zliczane osobno dla danych przesyłanych w obie strony, więc podanie wartości 1 oznacza, że wstępnie przydzielone są 1 bufor wysyłania i 1 bufor odbioru. Ogólnie liczba wymaganych buforów zależy od protokołu na wyższym poziomie między klientem a serwerem. W przypadku bardzo synchronicznego protokołu liczba ta może wynosić 1 (wyślij wiadomość, odbierz odpowiedź, zanim wyślesz kolejną). Liczba ta może być większa, jeśli klient chce wysłać więcej niż 1 wiadomość, zanim pojawi się odpowiedź (np.jedno wiadomość jako wstęp, a druga jako właściwe polecenie). Przydzielone zestawy buforów są przypisane do kanału, więc 2 osobne połączenia (kanały) miałyby osobne zestawy buforów.
[w] recv_buf_size
: maksymalny rozmiar każdego pojedynczego bufora w powyższym zbiorze. Ta wartość zależy od protokołu i skutecznie ogranicza maksymalny rozmiar wiadomości, które można wymieniać z peerem.
[in] flags
: kombinacja flag określająca dodatkowe zachowanie portu
Ta wartość powinna być kombinacją tych wartości:
IPC_PORT_ALLOW_TA_CONNECT
– zezwala na połączenie z innymi bezpiecznymi aplikacjami.
IPC_PORT_ALLOW_NS_CONNECT
– zezwala na połączenie z niebezpiecznego świata.
[retval]: wskaźnik do portu utworzonego, jeśli wartość jest nieujemna, lub konkretny błąd, jeśli jest ujemna.
Następnie serwer sprawdza listę uchwytów portów pod kątem przychodzących połączeń, używając wywołania wait()
. Po otrzymaniu prośby o nawiązanie połączenia, co jest sygnalizowane przez ustawiony bit IPC_HANDLE_POLL_READY
w polu event
struktury uevent_t
, serwer powinien wywołać funkcję accept()
, aby dokończyć nawiązywanie połączenia i utworzyć kanał (reprezentowany przez inny identyfikator), który może być następnie sondowany pod kątem przychodzących wiadomości.
accept()
Akceptuje połączenie przychodzące i otrzymuje identyfikator kanału.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: uchwyt reprezentujący port, do którego podłączony jest klient
[out] peer_uuid
: wskaźnik do struktury uuid_t
, która zostanie wypełniona identyfikatorem UUID aplikacji klienckiej nawiązującej połączenie. Jeśli połączenie pochodzi ze świata niezabezpieczonego, jest ustawione na zera.
[retval]: identyfikator kanału (jeśli nieujemny), na którym serwer może wymieniać wiadomości z klientem (w przeciwnym razie kod błędu)
Interfejs API klienta
Ta sekcja zawiera metody w interfejsie Client API.
Metody w interfejsie Client API
connect()
Rozpoczyna połączenie z portem określonym według nazwy.
long connect(const char *path, uint flags);
[in] path
: nazwa portu opublikowanego przez zaufaną aplikację
[in] flags
: określa dodatkowe, opcjonalne działanie
[retval]: identyfikator kanału, za pomocą którego można wymieniać wiadomości z serwerem; błąd, jeśli wartość jest ujemna
Jeśli nie określono żadnych parametrów flags
(parametry flags
mają wartość 0), wywołanie funkcji connect()
inicjuje połączenie synchroniczne z określonym portem, które natychmiast zwraca błąd, jeśli port nie istnieje, i tworzy blokadę, dopóki serwer nie zaakceptuje połączenia.
Aby zmienić to zachowanie, możesz określić kombinację 2 wartości:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
– wymusza, aby wywołanie connect()
czekało, jeśli określony port nie istnieje w momencie wykonania, zamiast od razu zakończyć się niepowodzeniem.
IPC_CONNECT_ASYNC
– jeśli jest ustawiona, inicjuje połączenie asynchroniczne. Aplikacja musi sprawdzać zwrócony identyfikator, wywołując funkcję wait()
, aby uzyskać zdarzenie zakończenia połączenia, które jest sygnalizowane przez bit IPC_HANDLE_POLL_READY
ustawiony w polu zdarzenia struktury uevent_t
przed rozpoczęciem normalnej pracy.
Messaging API
Wywołania interfejsu Messaging API umożliwiają wysyłanie i odczytywanie wiadomości za pomocą wcześniej nawiązanego połączenia (kanału). Wywołania interfejsu Messaging API są takie same w przypadku serwerów i klientów.
Klient otrzymuje identyfikator kanału, wykonując wywołanie connect()
, a serwer otrzymuje identyfikator kanału z wywołania accept()
, jak opisano powyżej.
Struktura wiadomości Trusty
Jak widać poniżej, wiadomości wymieniane przez interfejs Trusty API mają minimalną strukturę, co pozwala serwerowi i klientowi na uzgodnienie semantyki rzeczywistej zawartości:
/* * 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;
Wiadomość może składać się z co najmniej jednego nieciągłego bufora reprezentowanego przez tablicę struktur iovec_t
. Trusty wykonuje operacje odczytu i zapisu w tych blokach za pomocą tablicy iov
. Treści buforów, które można opisać za pomocą tablicy iov
, są całkowicie dowolne.
Metody w Messaging API
send_msg()
Wysyła wiadomość przez określony kanał.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: identyfikator kanału, przez który chcesz wysłać wiadomość
[in] msg
: wskaźnik do ipc_msg_t structure
opisujący wiadomość
[retval]: łączna liczba bajtów wysłanych w przypadku powodzenia; w przeciwnym razie błąd o wartości ujemnej
Jeśli klient (lub serwer) próbuje wysłać wiadomość przez kanał, a w kolejce wiadomości do węzła docelowego nie ma miejsca, kanał może przejść w stan blokady wysyłania (nie powinno się to zdarzyć w przypadku prostego synchronicznego protokołu żądanie/odpowiedź, ale może się zdarzyć w bardziej skomplikowanych przypadkach). Stan ten jest sygnalizowany przez zwrócenie kodu błędu ERR_NOT_ENOUGH_BUFFER
.
W takim przypadku wywołujący musi zaczekać, aż peer zwolnił trochę miejsca w kole do odbioru, pobierając wiadomości do obsługi i wycofania, co jest sygnalizowane przez bit IPC_HANDLE_POLL_SEND_UNBLOCKED
ustawiony w polu event
struktury uevent_t
zwracanej przez wywołanie wait()
.
get_msg()
Pobiera metadane dotyczące następnej wiadomości w kolejce wiadomości przychodzących.
określonego kanału.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: identyfikator kanału, z którego należy pobrać nową wiadomość
[out] msg_info
: struktura informacji wiadomości opisana w ten sposób:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Każdej wiadomości jest przypisywany unikalny identyfikator wśród zestawu oczekujących wiadomości, a łączna długość każdej wiadomości jest wypełniana. Jeśli skonfigurowano to i zezwala na to protokół, na danym kanale może być jednocześnie wiele otwartych wiadomości.
[retval]: NO_ERROR
w przypadku powodzenia, w przeciwnym razie błąd o wartości ujemnej.
read_msg()
Odczytuje zawartość wiadomości o określonym identyfikatorze, zaczynając od określonego przesunięcia.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: identyfikator kanału, z którego odczywana jest wiadomość
[w] msg_id
: identyfikator wiadomości do odczytania
[w] offset
: przesunięcie w wiadomości, od którego ma się rozpocząć czytanie
[out] msg
: wskaźnik do struktury ipc_msg_t
opisującej zestaw buforów, w których są przechowywane dane przychodzących wiadomości.
[retval]: łączna liczba bajtów przechowywanych w buforach msg
w przypadku powodzenia; w przeciwnym razie błąd o wartości ujemnej
Metodę read_msg
można wywołać wielokrotnie, rozpoczynając od innego (niekoniecznie sekwencyjnego) przesunięcia.
put_msg()
wycofuje wiadomość o określonym identyfikatorze.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: identyfikator kanału, na który dotarła wiadomość
[in] msg_id
: identyfikator wiadomości, która została wycofana
[retval]: NO_ERROR
w przypadku powodzenia, w przeciwnym razie błąd o wartości ujemnej.
Po wycofaniu wiadomości i zwolnieniu zajmowanego przez nią bufora nie można uzyskać dostępu do jej treści.
Interfejs API deskryptora pliku
Interfejs API deskryptora pliku obejmuje wywołania read()
, write()
i ioctl()
. Wszystkie te wywołania mogą działać na wstępnie zdefiniowanym (statycznym) zbiorze deskryptorów plików, tradycyjnie reprezentowanych przez małe liczby. W obecnej implementacji przestrzeń opisów plików jest oddzielona od przestrzeni interfejsu IPC. Interfejs File Descriptor API w Trusty jest podobny do tradycyjnego interfejsu API opartego na deskryptorze pliku.
Domyślnie dostępne są 3 wstępnie zdefiniowane (standardowe i znane) deskryptory plików:
- 0 – standardowe wejście. Domyślna implementacja standardowego wejścia
fd
jest bezczynna (ponieważ zaufane aplikacje nie mają interaktywnej konsoli), więc odczyt, zapisywanie ani wywoływanieioctl()
w wartościfd
0 powinny zwracać błądERR_NOT_SUPPORTED
. - 1 – standardowe wyjście. Dane zapisane w standardowym wyjściu mogą być kierowane (w zależności od poziomu debugowania LK) do UART lub do pliku dziennika pamięci dostępnego po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Niekrytyczne dzienniki debugowania i wiadomości powinny być wyświetlane w standardowym wyjściu. Metody
read()
iioctl()
nie wykonują żadnej operacji i powinny zwracać błądERR_NOT_SUPPORTED
. - 2 – błąd standardowy. Dane zapisywane w standardowym błędzie powinny być kierowane do UART lub do pliku dziennika pamięci dostępnego po stronie niezabezpieczonej (w zależności od platformy i konfiguracji). Zalecamy zapisywanie tylko ważnych komunikatów w standardowym błędzie, ponieważ ten strumień prawdopodobnie nie będzie ograniczony. Metody
read()
iioctl()
nie wykonują żadnej operacji i powinny zwracać błądERR_NOT_SUPPORTED
.
Chociaż ten zestaw deskryptorów plików można rozszerzyć, aby zaimplementować więcej rozszerzeń fds
(w celu zaimplementowania rozszerzeń dla konkretnych platform), należy zachować ostrożność podczas rozszerzania deskryptorów plików. Rozszerzanie deskryptorów plików może powodować konflikty i nie jest zalecane.
Metody interfejsu File Descriptor API
read()
Próbuje odczytać do count
bajtów danych z określonego deskryptora pliku.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: deskryptor pliku do odczytu
[out] buf
: wskaźnik do bufora, w którym są przechowywane dane
[in] count
: maksymalna liczba bajtów do odczytu
[retval]: zwracana liczba odczytanych bajtów; w przeciwnym razie zwracany jest błąd o wartości ujemnej.
write()
Zapisuje maksymalnie count
bajtów danych w określonym deskryptorze pliku.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: deskryptor pliku, do którego ma być zapisywany tekst
[out] buf
: wskaźnik do danych do zapisania
[in] count
: maksymalna liczba bajtów do zapisania
[retval]: zwracana liczba zapisanych bajtów; w przeciwnym razie zwracany jest błąd o wartości ujemnej.
ioctl()
Wywołuje określone polecenie ioctl
dla danego deskryptora pliku.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: deskryptor pliku, w którym ma być wywołana funkcja ioctl()
[w usłudze] cmd
: polecenie ioctl
[wejściowy/wyjściowy] args
: wskaźnik na argumenty ioctl()
Interfejsy API o różnym przeznaczeniu
Metody w interfejsie Miscellaneous API
gettime()
Zwraca bieżący czas systemowy (w nanosekundach).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: zależne od platformy; domyślnie wartość 0
[in] flags
: zarezerwowane, powinno być zero
[out] time
: wskaźnik do wartości int64_t
, w której jest przechowywany bieżący czas
[retval]: NO_ERROR
w przypadku powodzenia, w przeciwnym razie błąd o wartości ujemnej.
nanosleep()
Wstrzymuje wykonywanie aplikacji do wykonywania połączeń przez określony czas i wznawia je po upływie tego czasu.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: zarezerwowane, powinno być zero
[in] flags
: zarezerwowane, powinno być zero
[w] sleep_time
: czas snu w nanosekundach
[retval]: NO_ERROR
w przypadku powodzenia, w przeciwnym razie błąd o wartości ujemnej.
Przykład zaufanej aplikacji
Poniższa przykładowa aplikacja pokazuje sposób użycia powyższych interfejsów API. Przykład tworzy usługę „echo”, która obsługuje wiele połączeń przychodzących i przekazuje dzwoniącemu wszystkie wiadomości otrzymane od klientów pochodzących z bezpiecznej lub niezabezpieczonej strony.
#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; }
Metoda run_end_to_end_msg_test()
wysyła asynchronicznie 10 000 wiadomości do usługi „echo” i obsługuje odpowiedzi.
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; }
Niebezpieczne interfejsy API i aplikacje
Zbiór usług zaufanych, opublikowanych po bezpiecznej stronie i oznaczonych atrybutem IPC_PORT_ALLOW_NS_CONNECT
, jest dostępny dla programów jądra i przestrzeni użytkownika działających po niezabezpieczonej stronie.
Środowisko wykonawcze po stronie niezabezpieczonej (rdzeń i przestrzeń użytkownika) różni się znacznie od środowiska wykonawczego po stronie zabezpieczonej. Dlatego zamiast jednej biblioteki dla obu środowisk mamy 2 różne zestawy interfejsów API. W jądrze interfejs API klienta jest udostępniany przez jądrowy sterownik trusty-ipc i rejestruje węzeł urządzenia znakowego, którego procesy w przestrzeni użytkownika mogą używać do komunikacji z usługami działającymi po bezpiecznej stronie.
Interfejs Trusty IPC Client API w przestrzeni użytkownika
Biblioteka interfejsu API klienta Trusty IPC w przestrzeni użytkownika to cienka warstwa na wierzchu węzła urządzenia fd
.
Program w przestrzeni użytkownika inicjuje sesję komunikacji, wywołując funkcję tipc_connect()
, która inicjuje połączenie z określoną usługą Trusty. Wewnętrznie wywołanie tipc_connect()
otwiera określony węzeł urządzenia, aby uzyskać deskryptor pliku, i wywołuje wywołanie TIPC_IOC_CONNECT ioctl()
z parametrem argp
wskazującym na ciąg znaków zawierający nazwę usługi, z którą ma się połączyć.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Wygenerowany deskryptor pliku może być używany tylko do komunikacji z usługą, dla której został utworzony. Deskryptor pliku powinien zostać zamknięty przez wywołanie funkcji tipc_close()
, gdy połączenie nie jest już potrzebne.
Deskryptor pliku uzyskany przez wywołanie tipc_connect()
zachowuje się jak typowy węzeł urządzenia znakowego. Deskryptor pliku:
- W razie potrzeby można przełączyć na tryb nieblokujący.
- Możesz pisać do drugiej strony za pomocą standardowego połączenia
write()
- Może być sondowany (za pomocą wywołań
poll()
lubselect()
) w celu sprawdzenia dostępności przychodzących wiadomości jako zwykły deskryptor pliku. - może odczytać wiadomości przychodzące;
Rozmówca wysyła wiadomość do usługi Trusty, wykonując wywołanie do zapisu dla określonego fd
. Wszystkie dane przekazane do wywołania write()
są przekształcane w wiadomość przez moduł trusty-ipc. Wiadomość jest dostarczana do bezpiecznej strony, gdzie dane są obsługiwane przez podsystem IPC w rdzeniu Trusty, a następnie kierowane do właściwego miejsca docelowego i przekazywane do pętli zdarzeń aplikacji jako zdarzenie IPC_HANDLE_POLL_MSG
w ramach określonego kanału. W zależności od konkretnego protokołu usługi usługa Trusty może wysłać co najmniej 1 wiadomość z odpowiedzią, która jest odbierana z niechronionej strony i umieszczana w kolejce odpowiednich wiadomości z opisem pliku kanału, aby można było ją pobrać za pomocą wywołania aplikacji read()
w przestrzeni użytkownika.
tipc_connect()
Otwiera określony węzeł urządzenia tipc
i inicjuje połączenie z określoną usługą Trusty.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: ścieżka do otwieranego węzła urządzenia Trusty IPC
[in] srv_name
: nazwa opublikowanej usługi Trusty, z którą chcesz się połączyć
[retval]: prawidłowy deskryptor pliku w przypadku powodzenia, w przeciwnym razie -1.
tipc_close()
Zamyka połączenie z usługą Trusty określonej przez deskryptor pliku.
int tipc_close(int fd);
[in] fd
: deskryptor pliku otwartego wcześniej przez wywołanie tipc_connect()
Kernel Trusty IPC Client API
Interfejs Trusty IPC Client API jest dostępny dla sterowników jądra. Interfejs API Trusty IPC dla przestrzeni użytkownika jest implementowany na podstawie tego interfejsu API.
Ogólnie typowym zastosowaniem tego interfejsu API jest tworzenie przez wywołującego obiektu struct tipc_chan
za pomocą funkcji tipc_create_channel()
, a następnie inicjowanie połączenia z usługą Trusty IPC uruchomioną po bezpiecznej stronie za pomocą wywołania tipc_chan_connect()
. Połączenie ze stroną zdalną można zakończyć, wywołując tipc_chan_shutdown()
, a następnie tipc_chan_destroy()
, aby wyczyścić zasoby.
Po otrzymaniu powiadomienia (poprzez wywołanie zwrotne handle_event()
), że połączenie zostało nawiązane, dzwoniący:
- Pobiera bufor wiadomości za pomocą wywołania
tipc_chan_get_txbuf_timeout()
- Utwórz wiadomość, a
- umieszcza wiadomość w kolejce do wysłania za pomocą metody
tipc_chan_queue_msg()
do usługi zaufanej (po bezpiecznej stronie), z którą połączony jest kanał.
Po umieszczeniu w kolejce wywołujący powinien zapomnieć o buforze wiadomości, ponieważ po przetworzeniu przez stronę zdalną bufor wiadomości wraca do puli wolnych buforów (w celu ponownego użycia w przyszłości, w przypadku innych wiadomości). Użytkownik musi wywołać funkcję tipc_chan_put_txbuf()
tylko wtedy, gdy nie może umieścić takiego bufora w kolejce lub gdy nie jest on już potrzebny.
Użytkownik interfejsu API odbiera wiadomości z dalszego końca, obsługując wywołanie zwrotne powiadomienia handle_msg()
(które jest wywoływane w kontekście kolejki rx
trusty-ipc), które udostępnia wskaźnik do bufora rx
zawierającego przychodzącą wiadomość do obsłużenia.
Implementacja wywołania zwrotnego handle_msg()
powinna zwracać wskaźnik do prawidłowej wartości struct tipc_msg_buf
.
Może być taki sam jak bufor przychodzących wiadomości, jeśli jest obsługiwany lokalnie i nie jest już potrzebny. Może to być też nowy bufor uzyskany przez wywołanie tipc_chan_get_rxbuf()
, jeśli bufor przychodzący jest umieszczony w kolejce do dalszego przetwarzania. Odłączony bufor rx
musi być śledzony, a ostatecznie zwolniony za pomocą wywołania tipc_chan_put_rxbuf()
, gdy nie jest już potrzebny.
Metody interfejsu Kernel Trusty IPC Client API
tipc_create_channel()
Tworzy i konfiguruje instancję kanału Trusty IPC dla konkretnego urządzenia trusty-ipc.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: wskaźnik do trusty-ipc, dla którego utworzono kanał urządzenia
[in] ops
: wskaźnik do funkcji struct tipc_chan_ops
, w której wywołania zwrotne są wypełniane przez wywołującego.
[in] cb_arg
: wskaźnik do danych przekazywanych do wywołań zwrotnych tipc_chan_ops
[retval]: w razie powodzenia wskaźnik do nowo utworzonej instancji struct tipc_chan
, w przeciwnym razie ERR_PTR(err)
Na ogół wywołujący musi podać 2 wywołania zwrotne, które są wywoływane asynchronicznie, gdy występuje odpowiednia aktywność.
Zdarzenie void (*handle_event)(void *cb_arg, int event)
jest wywoływane, aby powiadomić wywołującego o zmianie stanu kanału.
[in] cb_arg
: wskaźnik do danych przekazanych do wywołania tipc_create_channel()
[in] event
: zdarzenie, które może mieć jedną z tych wartości:
TIPC_CHANNEL_CONNECTED
– wskazuje na pomyślne połączenie ze stroną zewnętrznąTIPC_CHANNEL_DISCONNECTED
– wskazuje, że strona zdalna odrzuciła nową prośbę o połączenie lub poprosiła o rozłączenie wcześniej połączonego kanału.TIPC_CHANNEL_SHUTDOWN
– wskazuje, że strona zdalna się wyłącza, trwale kończąc wszystkie połączenia.
Funkcja wywołania zwrotnego struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
jest wywoływana, aby powiadomić o otrzymaniu nowej wiadomości przez określony kanał:
- [in]
cb_arg
: wskaźnik do danych przekazanych do wywołaniatipc_create_channel()
- [in]
mb
: wskaźnik dostruct tipc_msg_buf
opisujący przychodzącą wiadomość - [retval]: Implementacja funkcji wywołania zwrotnego powinna zwracać wskaźnik do
struct tipc_msg_buf
, który może być tym samym wskaźnikiem otrzymanym jako parametrmb
, jeśli wiadomość jest obsługiwana lokalnie i nie jest już wymagana (lub może to być nowy bufor uzyskany przez wywołanietipc_chan_get_rxbuf()
).
tipc_chan_connect()
Inicjowanie połączenia z określoną usługą Trusty IPC.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: wskaźnik do kanału zwróconego przez wywołanie tipc_create_chan()
[in] port
: wskaźnik do ciągu znaków zawierającego nazwę usługi, z którą ma być nawiązane połączenie
[retval]: 0 w przypadku powodzenia, w przeciwnym razie błąd o wartości ujemnej.
Po nawiązaniu połączenia dzwoniący otrzymujehandle_event
połączenie zwrotne.
tipc_chan_shutdown()
Zawiesza połączenie z usługą Trusty IPC, które zostało wcześniej zainicjowane przez wywołanie tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[w] chan
: wskaźnik do kanału zwróconego przez wywołanie tipc_create_chan()
tipc_chan_destroy()
Niszczy określony kanał IPC Trusty.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: wskaźnik do kanału zwróconego przez wywołanie tipc_create_chan()
tipc_chan_get_txbuf_timeout()
Pobiera bufor wiadomości, który może służyć do wysyłania danych przez określony kanał. Jeśli bufor nie jest dostępny od razu, wywołujący może zostać zablokowany na określony czas oczekiwania (w milisekundach).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: wskaźnik do kanału, do którego ma zostać dodana wiadomość
[in] chan
: Maksymalny czas oczekiwania na dostępność bufora tx
[retval]: prawidłowy bufor wiadomości w przypadku powodzenia, ERR_PTR(err)
w przypadku błędu
tipc_chan_queue_msg()
Ustawia w kolejce wiadomość do wysłania przez określone kanały IPC zaufanych urządzeń.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[w] chan
: wskaźnik do kanału, do którego ma zostać dodana wiadomość
[in] mb:
Wskaźnik do wiadomości do kolejki (uzyskany przez wywołanie tipc_chan_get_txbuf_timeout()
)
[retval]: 0 w przypadku powodzenia, w przeciwnym razie błąd o wartości ujemnej.
tipc_chan_put_txbuf()
Zwalnia bufor wiadomości Tx
, który został wcześniej uzyskany przez wywołanie tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: wskaźnik do kanału, do którego należy ten bufor wiadomości
[in] mb
: wskaźnik do bufora wiadomości do zwolnienia
[retval]: brak
tipc_chan_get_rxbuf()
Pobiera nowy bufor wiadomości, który może służyć do odbierania wiadomości na określonym kanale.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: wskaźnik do kanału, do którego należy ten bufor wiadomości
[retval]: prawidłowy bufor wiadomości w przypadku powodzenia, ERR_PTR(err)
w przypadku błędu
tipc_chan_put_rxbuf()
Zwalnia określony bufor wiadomości, który został wcześniej uzyskany przez wywołanie tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: wskaźnik do kanału, do którego należy ten bufor wiadomości
[in] mb
: wskaźnik do bufora wiadomości do zwolnienia
[retval]: brak