Trusty udostępnia interfejsy API umożliwiające tworzenie dwóch klas aplikacji/usług:
- Zaufane aplikacje lub usługi działające na procesorze TEE
- Normalne/niezaufane aplikacje działające na głównym procesorze i korzystające z usług udostępnianych przez zaufane aplikacje
Trusty API ogólnie opisuje system komunikacji międzyprocesowej Trusty (IPC), w tym komunikację ze światem niezabezpieczonym. Oprogramowanie działające na głównym procesorze może wykorzystywać interfejsy Trusty API do łączenia się z zaufanymi aplikacjami/usługami i wymieniania z nimi dowolnych wiadomości, podobnie jak usługa sieciowa za pośrednictwem protokołu IP. Do aplikacji należy określenie formatu danych i semantyki tych wiadomości przy użyciu protokołu na poziomie aplikacji. Niezawodne dostarczanie komunikatów gwarantuje bazowa infrastruktura 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 aplikacje Trusty do udostępniania punktów końcowych usług w formie nazwanej ścieżki, z którą łączą się klienci. Daje to prosty identyfikator usługi oparty na ciągach znaków, z którego mogą korzystać klienci. Konwencja nazewnictwa to nazewnictwo w odwrotnym stylu DNS, np. com.google.servicename
.
Gdy klient łączy się z portem, otrzymuje kanał umożliwiający interakcję z usługą. Usługa musi zaakceptować połączenie przychodzące, a kiedy to zrobi, również otrzyma kanał. Zasadniczo porty służą do wyszukiwania usług, a następnie komunikacja odbywa się za pośrednictwem pary połączonych kanałów (tj. instancji połączenia na porcie). Kiedy klient łączy się z portem, ustanawiane jest symetryczne, dwukierunkowe połączenie. Korzystając ze ścieżki pełnego dupleksu, klienci i serwery mogą wymieniać dowolne komunikaty, dopóki którakolwiek ze stron nie zdecyduje się przerwać połączenia.
Tylko zaufane aplikacje po stronie bezpiecznej 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 bezpiecznej.
W zależności od wymagań zaufana aplikacja może być jednocześnie klientem i serwerem. Zaufana aplikacja publikująca usługę (jako serwer) może wymagać połączenia z innymi usługami (jako klient).
Obsługa API
Uchwyty to liczby całkowite bez znaku reprezentujące zasoby, takie jak porty i kanały, podobnie jak deskryptory plików w systemie UNIX. Po utworzeniu uchwytów są one umieszczane w tabeli uchwytów specyficznej dla aplikacji i można do nich później się odwoływać.
Osoba wywołująca może powiązać prywatne dane z uchwytem za pomocą metody set_cookie()
.
Metody w interfejsie API obsługi
Uchwyty obowiązują tylko w kontekście aplikacji. Aplikacja nie powinna przekazywać wartości uchwytu innym aplikacjom, chyba że zostało to wyraźnie określone. Wartość uchwytu należy interpretować jedynie poprzez porównanie jej z wartością INVALID_IPC_HANDLE #define,
której aplikacja może użyć jako wskazania, że uchwyt jest nieprawidłowy lub nieustawiony.
set_cookie()
Kojarzy prywatne dane dostarczone przez osobę wywołującą z określonym uchwytem.
long set_cookie(uint32_t handle, void *cookie)
[w] handle
: Dowolny uchwyt zwrócony przez jedno z wywołań API
[w] cookie
: Wskaźnik do dowolnych danych przestrzeni użytkownika w aplikacji Trusty
[retval]: NO_ERROR
w przypadku powodzenia, w przeciwnym wypadku kod błędu < 0
To wywołanie jest przydatne do obsługi zdarzeń, które wystąpią później po utworzeniu dojścia. Mechanizm obsługi zdarzeń dostarcza uchwyt i jego plik cookie z powrotem do procedury obsługi zdarzeń.
Na uchwyty zdarzeń można czekać za pomocą wywołania wait()
.
Czekać()
Czeka na wystąpienie zdarzenia na danym dojściu przez określony czas.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[w] handle_id
: Dowolny uchwyt zwrócony przez jedno z wywołań API
[out] event
: Wskaźnik do struktury reprezentującej zdarzenie, które wystąpiło w tym uchwycie
[in] timeout_msecs
: Wartość limitu czasu w milisekundach; wartość -1 oznacza nieskończony limit czasu
[retval]: NO_ERROR
jeśli w określonym przedziale czasu wystąpiło ważne zdarzenie; ERR_TIMED_OUT
jeśli upłynął określony limit czasu, ale nie wystąpiło żadne zdarzenie; < 0
dla innych błędów
W przypadku powodzenia ( retval == NO_ERROR
) wywołanie wait()
wypełnia określoną strukturę uevent_t
informacjami o zdarzeniu, które miało 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ę następujących 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
- w rzeczywistości żadne zdarzenia nie są w toku, 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, jak następuje:
- W przypadku portów ta wartość wskazuje, że istnieje oczekujące połączenie
- W przypadku kanałów wartość ta wskazuje, że nawiązano połączenie asynchroniczne (patrz
connect()
).
Poniższe zdarzenia dotyczą tylko kanałów:
-
IPC_HANDLE_POLL_HUP
- wskazuje, że kanał został zamknięty przez partnera -
IPC_HANDLE_POLL_MSG
- wskazuje, że dla tego kanału istnieje oczekująca wiadomość -
IPC_HANDLE_POLL_SEND_UNBLOCKED
- wskazuje, że dzwoniący, który wcześniej zablokował wysyłanie, może spróbować ponownie wysłać wiadomość (szczegóły w opisie funkcjisend_msg()
)
Procedura obsługi zdarzeń powinna być przygotowana do obsługi kombinacji określonych zdarzeń, ponieważ jednocześnie może być ustawionych wiele bitów. Na przykład w przypadku kanału możliwe jest jednoczesne zamknięcie oczekujących wiadomości i połączenia przez partnera równorzędnego.
Większość wydarzeń jest lepka. Trwają tak długo, jak długo utrzymuje się podstawowy warunek (na przykład odbierane są wszystkie oczekujące wiadomości i obsługiwane są oczekujące żądania połączeń). Wyjątkiem jest przypadek zdarzenia IPC_HANDLE_POLL_SEND_UNBLOCKED
, które przy odczycie jest kasowane i aplikacja ma tylko jedną szansę na obsłużenie tego zdarzenia.
Uchwyty można zniszczyć wywołując metodę close()
.
zamknąć()
Niszczy zasób powiązany z określonym uchwytem i usuwa go z tabeli uchwytów.
long close(uint32_t handle_id);
[w] handle_id
: Uchwyt do zniszczenia
[retval]: 0, jeśli się powiedzie; w przeciwnym razie błąd negatywny
API serwera
Serwer rozpoczyna od utworzenia jednego lub większej liczby nazwanych portów reprezentujących punkty końcowe jego usług. Każdy port jest reprezentowany przez uchwyt.
Metody w API serwera
port_utwórz()
Tworzy nazwany port usługi.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: Ciąg nazwy portu (jak opisano powyżej). Nazwa ta powinna być unikalna w całym systemie; próby utworzenia duplikatu nie powiodą się.
[in] num_recv_bufs
: Maksymalna liczba buforów, które kanał na tym porcie może wstępnie przydzielić, aby ułatwić wymianę danych z klientem. Bufory są liczone oddzielnie dla danych przesyłanych w obu kierunkach, więc podanie tutaj wartości 1 oznaczałoby, że wstępnie przydzielono 1 bufor wysyłania i 1 odbierania. Ogólnie rzecz biorąc, liczba wymaganych buforów zależy od umowy protokołu wyższego poziomu między klientem a serwerem. Liczba może wynosić zaledwie 1 w przypadku protokołu bardzo synchronicznego (wyślij wiadomość, odbierz odpowiedź przed wysłaniem kolejnej). Jednak liczba ta może być większa, jeśli klient spodziewa się wysłać więcej niż jedną wiadomość, zanim pojawi się odpowiedź (np. jedna wiadomość jako prolog, a druga jako właściwe polecenie). Przydzielone zestawy buforów są przypisane do każdego kanału, więc dwa oddzielne połączenia (kanały) będą miały oddzielne zestawy buforów.
[in] recv_buf_size
: Maksymalny rozmiar każdego pojedynczego bufora w powyższym zestawie buforów. Ta wartość jest zależna od protokołu i skutecznie ogranicza maksymalny rozmiar wiadomości, którą można wymieniać z peerem
[in] flags
: Kombinacja flag określająca dodatkowe zachowanie portu
Wartość ta powinna być kombinacją następujących wartości:
IPC_PORT_ALLOW_TA_CONNECT
- umożliwia połączenie z innych bezpiecznych aplikacji
IPC_PORT_ALLOW_NS_CONNECT
- umożliwia połączenie z niezabezpieczonego świata
[retval]: Uchwyt do utworzonego portu, jeśli jest nieujemny, lub konkretny błąd, jeśli jest ujemny
Następnie serwer odpytuje listę uchwytów portów dla połączeń przychodzących, używając wywołania wait()
. Po otrzymaniu żądania połączenia wskazywanego przez bit IPC_HANDLE_POLL_READY
ustawiony w polu event
struktury uevent_t
, serwer powinien wywołać funkcję accept()
, aby zakończyć nawiązywanie połączenia i utworzyć kanał (reprezentowany przez inny uchwyt), który będzie następnie mógł być odpytywany pod kątem przychodzących wiadomości .
zaakceptować()
Akceptuje połączenie przychodzące i uzyskuje uchwyt do kanału.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[w] handle_id
: Uchwyt reprezentujący port, do którego połączył się klient
[na zewnątrz] peer_uuid
: Wskaźnik do struktury uuid_t
, która ma zostać wypełniona identyfikatorem UUID łączącej się aplikacji klienckiej. Jeśli połączenie pochodzi z niezabezpieczonego świata, zostanie ustawione na same zera
[retval]: Uchwyt do kanału (jeśli nieujemny), na którym serwer może wymieniać komunikaty z klientem (lub w innym przypadku kod błędu)
API klienta
Ta sekcja zawiera metody w Client API.
Metody w Client API
łączyć()
Inicjuje połączenie z portem określonym przez nazwę.
long connect(const char *path, uint flags);
[w] path
: Nazwa portu opublikowana przez aplikację Trusty
[in] flags
: Określa dodatkowe, opcjonalne zachowanie
[retval]: Uchwyt do kanału, przez który można wymieniać wiadomości z serwerem; błąd, jeśli jest ujemny
Jeśli nie określono żadnych flags
(parametr flags
jest ustawiony na 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 blok do czasu, aż serwer zaakceptuje połączenie w inny sposób .
To zachowanie można zmienić, określając kombinację dwóch wartości opisanych poniżej:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
- wymusza oczekiwanie wywołania connect()
, jeśli określony port nie istnieje od razu podczas wykonywania, zamiast natychmiastowego niepowodzenia.
IPC_CONNECT_ASYNC
- jeśli ustawione, inicjuje połączenie asynchroniczne. Aplikacja musi odpytać o zwrócony uchwyt (poprzez wywołanie funkcji wait()
w celu uzyskania zdarzenia zakończenia połączenia wskazywanego przez bit IPC_HANDLE_POLL_READY
ustawiony w polu zdarzenia struktury uevent_t
przed rozpoczęciem normalnej pracy).
Interfejs API przesyłania wiadomości
Wywołania API Messaging umożliwiają wysyłanie i odczytywanie wiadomości poprzez wcześniej nawiązane połączenie (kanał). Wywołania API Messaging są takie same dla serwerów i klientów.
Klient otrzymuje uchwyt do kanału poprzez wywołanie funkcji connect()
, a serwer otrzymuje uchwyt kanału poprzez wywołanie accept()
opisane powyżej.
Struktura wiadomości Trusty
Jak pokazano poniżej, wiadomości wymieniane przez Trusty API mają minimalną strukturę, pozostawiając serwerowi i klientowi uzgodnienie semantyki rzeczywistej treś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;
Komunikat może składać się z jednego lub większej liczby nieciągłych buforów reprezentowanych przez tablicę struktur iovec_t
. Trusty dokonuje odczytu i zapisu w tych blokach metodą scatter-gather przy użyciu tablicy iov
. Zawartość buforów, które można opisać tablicą iov
, jest całkowicie dowolna.
Metody w interfejsie API przesyłania wiadomości
wyślij_msg()
Wysyła wiadomość przez określony kanał.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: Uchwyt kanału, przez który ma zostać wysłana wiadomość
[in] msg
: Wskaźnik do ipc_msg_t structure
opisującej wiadomość
[retval]: Całkowita liczba bajtów wysłanych w przypadku powodzenia; w przeciwnym razie błąd negatywny
Jeśli klient (lub serwer) próbuje wysłać wiadomość przez kanał i nie ma miejsca w kolejce docelowych komunikatów równorzędnych, kanał może wejść w stan zablokowania wysyłania (co nie powinno nigdy mieć miejsca w przypadku prostego synchronicznego protokołu żądania/odpowiedzi ale może się to zdarzyć w bardziej skomplikowanych przypadkach), co jest sygnalizowane zwróceniem kodu błędu ERR_NOT_ENOUGH_BUFFER
. W takim przypadku wywołujący musi poczekać, aż peer zwolni trochę miejsca w swojej kolejce odbiorczej, pobierając komunikaty dotyczące obsługi i wycofywania, na co wskazuje ustawiony bit IPC_HANDLE_POLL_SEND_UNBLOCKED
w polu event
struktury uevent_t
zwróconej przez wywołanie wait()
.
get_msg()
Pobiera metainformacje o 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
: Uchwyt kanału, na którym ma zostać pobrana nowa wiadomość
[out] msg_info
: Struktura informacji wiadomości opisana poniżej:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Każdej wiadomości przypisany jest unikalny identyfikator w zestawie zaległych wiadomości i wypełniana jest całkowita długość każdej wiadomości. Jeśli protokół jest skonfigurowany i pozwala na to, dla określonego kanału może istnieć wiele zaległych (otwartych) wiadomości jednocześnie.
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd negatywny
przeczytaj_msg()
Odczytuje treść komunikatu 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);
[w] handle
: Uchwyt kanału, z którego ma zostać odczytana wiadomość
[w] msg_id
: Identyfikator wiadomości do przeczytania
[w] offset
: Przesunięcie w stosunku do wiadomości, od której należy rozpocząć czytanie
[out] msg
: Wskaźnik do struktury ipc_msg_t
opisującej zestaw buforów, w których przechowywane są dane wiadomości przychodzących
[retval]: Całkowita liczba bajtów przechowywanych w buforach msg
w przypadku powodzenia; w przeciwnym razie błąd negatywny
Metodę read_msg
można wywołać wielokrotnie, zaczynając od innego (niekoniecznie sekwencyjnego) przesunięcia, jeśli zajdzie taka potrzeba.
put_msg()
Wycofuje wiadomość o określonym identyfikatorze.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: Uchwyt kanału, na którym wiadomość dotarła
[w] msg_id
: Identyfikator wycofywanej wiadomości
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd negatywny
Nie można uzyskać dostępu do treści wiadomości po wycofaniu wiadomości i zwolnieniu zajmowanego przez nią bufora.
API deskryptora pliku
Interfejs API deskryptora pliku zawiera wywołania read()
, write()
i ioctl()
. Wszystkie te wywołania mogą działać na predefiniowanym (statycznym) zestawie deskryptorów plików, tradycyjnie reprezentowanych przez małe liczby. W bieżącej implementacji przestrzeń deskryptora pliku jest oddzielona od przestrzeni uchwytu IPC. Interfejs API deskryptorów plików w Trusty jest podobny do tradycyjnego interfejsu API opartego na deskryptorach plików.
Domyślnie dostępne są 3 predefiniowane (standardowe i dobrze znane) deskryptory plików:
- 0 - wejście standardowe. Domyślna implementacja standardowego wejścia
fd
nie daje żadnego efektu (ponieważ nie oczekuje się, że zaufane aplikacje będą miały interaktywną konsolę), więc czytanie, zapisywanie lub wywoływanieioctl()
nafd
0 powinno zwrócić błądERR_NOT_SUPPORTED
. - 1 - wyjście standardowe. Dane zapisane na standardowe wyjście mogą być kierowane (w zależności od poziomu debugowania LK) do UART i/lub dziennika pamięci dostępnego po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Niekrytyczne dzienniki debugowania i komunikaty powinny trafiać na standardowe wyjście. Metody
read()
iioctl()
nie dają żadnego efektu i powinny zwrócić błądERR_NOT_SUPPORTED
. - 2 - błąd standardowy. Dane zapisane na błąd standardowy powinny być kierowane do UART lub dziennika pamięci dostępnego po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Zaleca się zapisywanie tylko krytycznych komunikatów do standardowego błędu, ponieważ z dużym prawdopodobieństwem strumień ten nie będzie ograniczany. Metody
read()
iioctl()
nie dają żadnego efektu i powinny zwrócić błądERR_NOT_SUPPORTED
.
Mimo że ten zestaw deskryptorów plików można rozszerzyć, aby zaimplementować więcej fds
(w celu zaimplementowania rozszerzeń specyficznych dla platformy), z rozszerzaniem deskryptorów plików należy postępować ostrożnie. Rozszerzanie deskryptorów plików może powodować konflikty i ogólnie nie jest zalecane.
Metody w interfejsie API deskryptora pliku
Czytać()
Próbuje odczytać count
bajtów danych z określonego deskryptora pliku.
long read(uint32_t fd, void *buf, uint32_t count);
[w] fd
: Deskryptor pliku, z którego należy czytać
[out] buf
: Wskaźnik do bufora, w którym przechowywane są dane
[in] count
: Maksymalna liczba bajtów do odczytania
[retval]: Zwracana liczba przeczytanych bajtów; w przeciwnym razie błąd negatywny
pisać()
Zapisuje count
liczbę bajtów danych do określonego deskryptora pliku.
long write(uint32_t fd, void *buf, uint32_t count);
[w] fd
: Deskryptor pliku, do którego należy pisać
[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 błąd negatywny
ioctl()
Wywołuje określone polecenie ioctl
dla danego deskryptora pliku.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[w] fd
: Deskryptor pliku, na którym można wywołać ioctl()
[w] cmd
: Polecenie ioctl
[in/out] args
: Wskaźnik do argumentów ioctl()
Różne API
Metody w Miscellaneous API
uzyskać czas()
Zwraca bieżący czas systemowy (w nanosekundach).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[w] clock_id
: Zależne od platformy; przekazać zero jako domyślne
[w] flags
: zarezerwowane, powinno wynosić zero
[out] time
: Wskaźnik do wartości int64_t
, w której ma być przechowywany bieżący czas
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd negatywny
nanosen()
Zawiesza wykonywanie aplikacji wywołującej na określony czas i wznawia je po upływie tego czasu.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[w] clock_id
: Zarezerwowane, powinno wynosić zero
[w] flags
: zarezerwowane, powinno wynosić zero
[w] sleep_time
: Czas snu w nanosekundach
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd negatywny
Przykład zaufanego serwera aplikacji
Poniższa przykładowa aplikacja przedstawia użycie powyższych interfejsów API. Przykład tworzy usługę „echo”, która obsługuje wiele połączeń przychodzących i odzwierciedla wywołującemu wszystkie wiadomości otrzymane od klientów, pochodzące ze strony bezpiecznej lub niezabezpieczonej.
#include <uapi/err.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <trusty_ipc.h> #define LOG_TAG "echo_srv" #define TLOGE(fmt, ...) \ fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ##__VA_ARGS__) # define MAX_ECHO_MSG_SIZE 64 static const char * srv_name = "com.android.echo.srv.echo"; static uint8_t msg_buf[MAX_ECHO_MSG_SIZE]; /* * Message handler */ static int handle_msg(handle_t chan) { int rc; struct iovec iov; ipc_msg_t msg; ipc_msg_info_t msg_inf; iov.iov_base = msg_buf; iov.iov_len = sizeof(msg_buf); msg.num_iov = 1; msg.iov = &iov; msg.num_handles = 0; msg.handles = NULL; /* get message info */ rc = get_msg(chan, &msg_inf); if (rc == ERR_NO_MSG) return NO_ERROR; /* no new messages */ if (rc != NO_ERROR) { TLOGE("failed (%d) to get_msg for chan (%d)\n", rc, chan); return rc; } /* read msg content */ rc = read_msg(chan, msg_inf.id, 0, &msg); if (rc < 0) { TLOGE("failed (%d) to read_msg for chan (%d)\n", rc, chan); return rc; } /* update number of bytes received */ iov.iov_len = (size_t) rc; /* send message back to the caller */ rc = send_msg(chan, &msg); if (rc < 0) { TLOGE("failed (%d) to send_msg for chan (%d)\n", rc, chan); return rc; } /* retire message */ rc = put_msg(chan, msg_inf.id); if (rc != NO_ERROR) { TLOGE("failed (%d) to put_msg for chan (%d)\n", rc, chan); return rc; } return NO_ERROR; } /* * Channel event handler */ static void handle_channel_event(const uevent_t * ev) { int rc; if (ev->event & IPC_HANDLE_POLL_MSG) { rc = handle_msg(ev->handle); if (rc != NO_ERROR) { /* report an error and close channel */ TLOGE("failed (%d) to handle event on channel %d\n", rc, ev->handle); close(ev->handle); } return; } if (ev->event & IPC_HANDLE_POLL_HUP) { /* closed by peer. */ close(ev->handle); return; } } /* * Port event handler */ static void handle_port_event(const uevent_t * ev) { uuid_t peer_uuid; if ((ev->event & IPC_HANDLE_POLL_ERROR) || (ev->event & IPC_HANDLE_POLL_HUP) || (ev->event & IPC_HANDLE_POLL_MSG) || (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) { /* should never happen with port handles */ TLOGE("error event (0x%x) for port (%d)\n", ev->event, ev->handle); abort(); } if (ev->event & IPC_HANDLE_POLL_READY) { /* incoming connection: accept it */ int rc = accept(ev->handle, &peer_uuid); if (rc < 0) { TLOGE("failed (%d) to accept on port %d\n", rc, ev->handle); return; } handle_t chan = rc; while (true){ struct uevent cev; rc = wait(chan, &cev, INFINITE_TIME); if (rc < 0) { TLOGE("wait returned (%d)\n", rc); abort(); } handle_channel_event(&cev); if (cev.event & IPC_HANDLE_POLL_HUP) { return; } } } } /* * Main application entry point */ int main(void) { int rc; handle_t port; /* Initialize service */ rc = port_create(srv_name, 1, MAX_ECHO_MSG_SIZE, IPC_PORT_ALLOW_NS_CONNECT | IPC_PORT_ALLOW_TA_CONNECT); if (rc < 0) { TLOGE("Failed (%d) to create port %s\n", rc, srv_name); abort(); } port = (handle_t) rc; /* enter main event loop */ while (true) { uevent_t ev; ev.handle = INVALID_IPC_HANDLE; ev.event = 0; ev.cookie = NULL; /* wait forever */ rc = wait(port, &ev, INFINITE_TIME); if (rc == NO_ERROR) { /* got an event */ handle_port_event(&ev); } else { TLOGE("wait returned (%d)\n", rc); abort(); } } return 0; }
Metoda run_end_to_end_msg_test()
wysyła asynchronicznie 10 000 komunikatów 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; }
Niezabezpieczone światowe interfejsy API i aplikacje
Zestaw usług Trusty, publikowanych od strony bezpiecznej i oznaczonych atrybutem IPC_PORT_ALLOW_NS_CONNECT
, jest dostępny dla programów jądra i przestrzeni użytkownika działających po stronie niezabezpieczonej.
Środowisko wykonawcze po stronie niezabezpieczonej (jądro i przestrzeń użytkownika) drastycznie różni się od środowiska wykonawczego po stronie bezpiecznej. Dlatego zamiast pojedynczej biblioteki dla obu środowisk istnieją dwa różne zestawy interfejsów API. W jądrze interfejs API klienta jest udostępniany przez sterownik jądra trusty-ipc i rejestruje węzeł urządzenia znakowego, który może być używany przez procesy przestrzeni użytkownika do komunikowania się z usługami działającymi po bezpiecznej stronie.
Przestrzeń użytkownika Trusty IPC Client API
Biblioteka interfejsu API klienta Trusty IPC w przestrzeni użytkownika to cienka warstwa na wierzchu węzła urządzenia fd
.
Program przestrzeni użytkownika rozpoczyna sesję komunikacyjną poprzez wywołanie tipc_connect()
, inicjując połączenie z określoną usługą Trusty. Wewnętrznie wywołanie tipc_connect()
otwiera określony węzeł urządzenia w celu uzyskania deskryptora pliku i wywołuje wywołanie TIPC_IOC_CONNECT ioctl()
z parametrem argp
wskazującym ciąg znaków zawierający nazwę usługi, z którą należy się połączyć.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Powstały deskryptor pliku może być używany wyłącznie do komunikacji z usługą, dla której został utworzony. Deskryptor pliku powinien zostać zamknięty poprzez wywołanie 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żna zapisać przy użyciu standardowego
write()
w celu wysyłania wiadomości na drugą stronę - Można odpytywać (za pomocą wywołań
poll()
lub wywołańselect()
) pod kątem dostępności wiadomości przychodzących jako zwykły deskryptor pliku - Można odczytać, aby pobrać przychodzące wiadomości
Osoba wywołująca wysyła wiadomość do usługi Trusty, wykonując wywołanie zapisu dla określonego fd
. Wszystkie dane przekazane do powyższego wywołania write()
są przekształcane w komunikat przez sterownik trusty-ipc. Wiadomość jest dostarczana na bezpieczną stronę, gdzie dane są obsługiwane przez podsystem IPC w jądrze Trusty i kierowane do odpowiedniego miejsca docelowego oraz dostarczane do pętli zdarzeń aplikacji jako zdarzenie IPC_HANDLE_POLL_MSG
na określonym uchwycie kanału. W zależności od konkretnego protokołu specyficznego dla usługi, usługa Trusty może wysłać jedną lub więcej wiadomości odpowiedzi, które są dostarczane z powrotem do strony niezabezpieczonej i umieszczane w kolejce komunikatów deskryptora pliku odpowiedniego kanału w celu pobrania przez aplikację przestrzeni użytkownika read()
zadzwoń.
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);
[w] dev_name
: Ścieżka do węzła urządzenia Trusty IPC do otwarcia
[in] srv_name
: Nazwa opublikowanej usługi Trusty, z którą można się połączyć
[retval]: Poprawny deskryptor pliku w przypadku powodzenia, w przeciwnym razie -1.
tipc_close()
Zamyka połączenie z usługą Trusty określone przez deskryptor pliku.
int tipc_close(int fd);
[w] fd
: Deskryptor pliku poprzednio otwarty przez wywołanie tipc_connect()
API klienta Kernel Trusty IPC
Dla sterowników jądra dostępny jest interfejs API klienta Trusty IPC Client. Na tym API zaimplementowano przestrzeń użytkownika Trusty IPC API.
Ogólnie rzecz biorąc, typowe użycie tego API polega na tym, że wywołujący tworzy obiekt struct tipc_chan
za pomocą funkcji tipc_create_channel()
, a następnie używa wywołania tipc_chan_connect()
w celu zainicjowania połączenia z usługą Trusty IPC działającą po bezpiecznej stronie. Połączenie ze zdalną stroną można zakończyć, wywołując metodę tipc_chan_shutdown()
a następnie tipc_chan_destroy()
w celu oczyszczenia zasobów.
Po otrzymaniu powiadomienia (poprzez wywołanie zwrotne handle_event()
), że połączenie zostało pomyślnie nawiązane, osoba wywołująca wykonuje następujące czynności:
- Uzyskuje bufor komunikatów za pomocą wywołania
tipc_chan_get_txbuf_timeout()
- Tworzy wiadomość i
- Kolejkuje wiadomość przy użyciu metody
tipc_chan_queue_msg()
w celu dostarczenia do usługi Trusty (po bezpiecznej stronie), z którą połączony jest kanał
Po pomyślnym umieszczeniu w kolejce obiekt wywołujący powinien zapomnieć o buforze komunikatów, ponieważ bufor komunikatów ostatecznie wraca do puli wolnych buforów po przetworzeniu przez stronę zdalną (w celu późniejszego ponownego wykorzystania w przypadku innych komunikatów). Użytkownik musi wywołać funkcję tipc_chan_put_txbuf()
tylko wtedy, gdy nie uda mu się ustawić takiego bufora w kolejce lub nie jest on już potrzebny.
Użytkownik API odbiera komunikaty ze strony zdalnej, obsługując wywołanie zwrotne powiadamiające handle_msg()
(które jest wywoływane w kontekście kolejki roboczej trusty-ipc rx
), które dostarcza wskaźnik do bufora rx
zawierającego przychodzący komunikat do obsługi.
Oczekuje się, że implementacja wywołania zwrotnego handle_msg()
zwróci wskaźnik do prawidłowej struct tipc_msg_buf
. Może być taki sam jak bufor wiadomości przychodzących, jeśli jest obsługiwany lokalnie i nie jest już wymagany. Alternatywnie może to być nowy bufor uzyskany przez wywołanie tipc_chan_get_rxbuf()
, jeśli przychodzący bufor znajduje się w kolejce do dalszego przetwarzania. Odłączony bufor rx
musi być śledzony i ostatecznie zwalniany za pomocą wywołania tipc_chan_put_rxbuf()
, gdy nie jest już potrzebny.
Metody w API klienta Kernel Trusty IPC
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 zaufanego-ipc, dla którego utworzono kanał urządzenia
[in] ops
: Wskaźnik do struct tipc_chan_ops
z wypełnionymi wywołaniami zwrotnymi specyficznymi dla wywołującego
[w] cb_arg
: Wskaźnik do danych, które zostaną przekazane do wywołań zwrotnych tipc_chan_ops
[retval]: Wskaźnik do nowo utworzonej instancji struct tipc_chan
w przypadku powodzenia, w przeciwnym razie ERR_PTR(err)
Ogólnie rzecz biorąc, obiekt wywołujący musi udostępnić dwa wywołania zwrotne, które są wywoływane asynchronicznie, gdy występuje odpowiednie działanie.
Zdarzenie void (*handle_event)(void *cb_arg, int event)
jest wywoływane w celu powiadomienia osoby wywołującej o zmianie stanu kanału.
[w] cb_arg
: Wskaźnik do danych przekazanych do wywołania tipc_create_channel()
[in] event
: Zdarzenie, które może mieć jedną z następujących wartości:
-
TIPC_CHANNEL_CONNECTED
- wskazuje pomyślne połączenie ze stroną zdalną -
TIPC_CHANNEL_DISCONNECTED
- wskazuje, że strona zdalna odrzuciła żądanie nowego połączenia lub zażądała rozłączenia wcześniej połączonego kanału -
TIPC_CHANNEL_SHUTDOWN
— wskazuje, że strona zdalna zamyka się, trwale kończąc wszystkie połączenia
Wywołanie zwrotne struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
jest wywoływane w celu powiadomienia o odebraniu nowej wiadomości na określonym kanale:
- [w]
cb_arg
: Wskaźnik do danych przekazanych do wywołaniatipc_create_channel()
- [in]
mb
: Wskaźnik dostruct tipc_msg_buf
opisującej przychodzącą wiadomość - [retval]: Oczekuje się, że implementacja wywołania zwrotnego zwróci 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 przeztipc_chan_get_rxbuf()
wywołanie)
tipc_chan_connect()
Inicjuje połączenie 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 zawierającego nazwę usługi, z którą należy się połączyć
[retval]: 0 w przypadku powodzenia, w przeciwnym razie błąd ujemny
Osoba wywołująca jest powiadamiana o nawiązaniu połączenia poprzez wywołanie zwrotne handle_event
.
tipc_chan_shutdown()
Kończy połączenie z usługą Trusty IPC zainicjowane wcześniej przez wywołanie tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: Wskaźnik do kanału zwróconego przez wywołanie tipc_create_chan()
tipc_chan_destroy()
Niszczy określony kanał Trusty IPC.
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()
Uzyskuje bufor komunikatów, którego można użyć do wysyłania danych za pośrednictwem określonego kanału. Jeśli bufor nie jest natychmiast dostępny, obiekt wywołujący może zostać zablokowany na określony limit czasu (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 należy kolejkować wiadomość
[in] chan
: Maksymalny limit czasu oczekiwania, aż bufor tx
stanie się dostępny
[retval]: Prawidłowy bufor komunikatów w przypadku powodzenia, ERR_PTR(err)
w przypadku błędu
tipc_chan_queue_msg()
Kolejkuje wiadomość do wysłania przez określone kanały Trusty IPC.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Wskaźnik do kanału, do którego należy kolejkować wiadomość
[w] 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 ujemny
tipc_chan_put_txbuf()
Zwalnia określony bufor komunikatów Tx
uzyskany wcześniej 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 komunikatów
[in] mb
: Wskaźnik do bufora komunikatów do zwolnienia
[retval]: Brak
tipc_chan_get_rxbuf()
Uzyskuje nowy bufor komunikatów, którego można używać do odbierania komunikatów za pośrednictwem określonego kanału.
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 komunikatów
[retval]: Prawidłowy bufor komunikatów w przypadku powodzenia, ERR_PTR(err)
w przypadku błędu
tipc_chan_put_rxbuf()
Zwalnia określony bufor komunikatów uzyskany wcześniej 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 komunikatów
[in] mb
: Wskaźnik do bufora komunikatów do zwolnienia
[retval]: Brak