Trusty zapewnia interfejsy API do tworzenia 2 klas aplikacji/usług:
- Zaufane aplikacje lub usługi, które działają na procesorze TEE
- Zwykłe lub niezaufane aplikacje, które działają na głównym procesorze i korzystają z udostępnionych usług przez zaufane aplikacje
Trusty API ogólnie opisuje system komunikacji międzyprocesowej Trusty (IPC). także komunikacji z niezabezpieczonym światem. Oprogramowanie działające na główny procesor może używać interfejsów Trusty API do łączenia się z zaufanymi aplikacjami/usługami i wymieniać z nimi dowolne wiadomości, tak jak w przypadku usługi sieciowej przez IP. To aplikacja określa format i semantykę tych danych za pomocą protokołu na poziomie aplikacji. Niezawodne dostarczanie wiadomości gwarantowane przez bazową infrastrukturę Trusty (w postaci czynników 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ługi w formularzu
nazwanej ścieżki, z którą łączą się klienty. Daje to prosty, oparty na ciągach znaków
identyfikator usługi, którego mają używać klienci. Konwencja nazewnictwa jest zgodna z odwrotnym stylem DNS
nazywanie, np. com.google.servicename
Gdy klient łączy się z portem, otrzymuje kanał do interakcji z usługą. Usługa musi akceptować połączenie przychodzące oraz ale również odbiera kanał. Porty są używane do wyszukiwania usług a potem komunikacja odbywa się przez parę połączonych kanałów (tj. instancji połączeń na porcie). Gdy klient łączy się z portem, nawiązywane jest połączenie dwukierunkowe. Przy użyciu tej ścieżki a serwery mogą wymieniać dowolne wiadomości, dopóki jedna ze stron nie zdecyduje się mogą przerwać połączenie.
Mogą tworzyć tylko zaufane aplikacje po stronie bezpiecznej lub moduły jądra Trusty porty. Aplikacje uruchomione po niezabezpieczonej stronie (w normalnym świecie) mogą łączyć się tylko z usługami opublikowanymi przez bezpieczną stronę.
W zależności od wymagań zaufana aplikacja może być zarówno klientem, jak i jednocześnie z serwerem. Zaufana aplikacja, która publikuje usługę (jako serwera) może być konieczne nawiązanie połączenia z innymi usługami (jako klient).
Interfejs API obsługi uchwytu
Nicki to niepodpisane liczby całkowite reprezentujące zasoby, takie jak porty, podobne do deskryptorów plików w systemie UNIX. Po utworzeniu nicków są umieszczane w tabeli uchwytów specyficznych dla aplikacji i można się do nich odwoływać później.
Rozmówca może powiązać prywatne dane z nickiem za pomocą
metodę set_cookie()
.
Metody w interfejsie Handle API
Nicki są prawidłowe tylko w kontekście aplikacji. Aplikacja powinna
nie przekazują wartości nicka do innych aplikacji, chyba że wyraźnie
określone dane. Wartość nicka należy interpretować tylko przez porównanie jej z
INVALID_IPC_HANDLE #define,
, którego aplikacja może używać jako
wskazują, że nick jest nieprawidłowy lub nieskonfigurowany.
set_cookie()
Łączy prywatne dane przekazywane przez dzwoniącego z określonym nickiem.
long set_cookie(uint32_t handle, void *cookie)
[w] handle
: dowolny nick zwrócony przez jedno z wywołań interfejsu API
[in] cookie
: wskaźnik do dowolnych danych z przestrzeni użytkownika w aplikacji Trusty
[retval]: NO_ERROR
– powodzenie, < 0
kod błędu w innym przypadku
To wywołanie jest przydatne do obsługi zdarzeń, które pojawiają się w późniejszym czasie po i utworzyliśmy nick. Mechanizm obsługi zdarzeń dostarcza nick a jego plik cookie z powrotem do modułu obsługi zdarzeń.
Nicki można oczekiwać na zdarzenia przy użyciu wywołania wait()
.
czekaj()
Czekam na wystąpienie zdarzenia dla danego nicka przez określony czas.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[w] handle_id
: dowolny nick zwrócony przez jedno z wywołań interfejsu API
[out] event
: wskaźnik do struktury reprezentującej
zdarzenie, które wystąpiło na tym nicku
[w] timeout_msecs
: limit czasu w milisekundach; w
wartość -1 oznacza nieskończony czas oczekiwania
[retval]: NO_ERROR
, jeśli w ciągu
określony interwał czasu oczekiwania; ERR_TIMED_OUT
, jeśli upłynął określony czas oczekiwania, ale nie
miało miejsce zdarzenie; < 0
w przypadku innych błędów
Po sukcesie (retval == NO_ERROR
) wywołanie wait()
wypełnia określoną strukturę uevent_t
informacjami o
zdarzenia, 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ę 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 są oczekujące,
rozmówca powinien ponownie uruchomić oczekiwanie
IPC_HANDLE_POLL_ERROR
– wystąpił nieokreślony błąd wewnętrzny
IPC_HANDLE_POLL_READY
– zależy od typu nicka:
- W przypadku portów ta wartość oznacza, że istnieje oczekujące połączenie.
- W przypadku kanałów ta wartość oznacza, że połączenie asynchroniczne
(patrz:
connect()
) został utworzony
Te zdarzenia mają znaczenie tylko w przypadku kanałów:
IPC_HANDLE_POLL_HUP
– oznacza, że kanał został zamknięty przez innego użytkownika.IPC_HANDLE_POLL_MSG
– wskazuje, że na kanale istnieje oczekująca wiadomość.IPC_HANDLE_POLL_SEND_UNBLOCKED
– wskazuje, że poprzednio rozmówca zablokowany przy wysyłaniu może próbować wysłać ponownie wysłać wiadomość (szczegóły znajdziesz w opisie aplikacjisend_msg()
)
Moduł obsługi zdarzeń powinien być przygotowany do obsługi kombinacji zdarzeń, bo w tym samym czasie można ustawić większą liczbę bitów. Na przykład w przypadku adresu kanał może mieć oczekujące wiadomości i połączenie zamknięte przez jednocześnie z innymi użytkownikami.
Większość zdarzeń jest przyklejona. Utrzymują się, dopóki określony stan
nie ustępuje (na przykład wszystkie oczekujące wiadomości zostały odebrane i oczekujące na połączenie)
obsługi żądań). Wyjątkiem jest
zdarzenie IPC_HANDLE_POLL_SEND_UNBLOCKED
, które
jest oczyszczona po odczytaniu, a aplikacja ma tylko jedną szansę
sobie z tym radzi.
Nicki można zniszczyć przez wywołanie metody close()
.
zamknij()
Niszczy zasób powiązany z określonym nickiem i usuwa go z tabeli uchwytów.
long close(uint32_t handle_id);
[in] handle_id
: uchwyt do zniszczenia
[retval]: 0 – jeśli operacja się udała; w przeciwnym razie błąd ujemny
Interfejs API serwera
Serwer zaczyna od utworzenia co najmniej jednego nazwanego portu reprezentującego i punktach końcowych usług. Każdy port jest reprezentowany przez nick.
Metody w interfejsie Server API
port_create()
Tworzy nazwany port usługi.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[w] path
: nazwa ciągu tekstowego portu (zgodnie z opisem powyżej). Ten
nazwa powinna być unikalna w całym systemie, próby utworzenia duplikatu zakończą się niepowodzeniem.
[w] num_recv_bufs
: maksymalna liczba buforów na kanale
ten port może wstępnie przydzielone, aby ułatwić wymianę danych z klientem. Bufory są liczone
oddzielnie dla danych przechodzących w obie strony, więc określenie 1 w tym miejscu oznacza 1
wstępnie przydzielone są 1 bufor wysyłania i 1 bufor odbierania. Ogólnie rzecz biorąc, liczba buforów
wymagane zależy od umowy protokołu wyższego poziomu między klientem a
serwera. W przypadku bardzo synchronicznego protokołu liczba może wynosić maksymalnie 1.
(wyślij wiadomość, otrzymaj odpowiedź przed wysłaniem kolejnej). Ale numer może być
więcej, jeśli klient spodziewa się wysłać więcej niż jedną wiadomość, zanim odpowiedź
(np.jedna wiadomość jako prolog, a druga jako właściwe polecenie).
przydzielone zestawy buforów są przypisane do poszczególnych kanałów, a więc 2 osobne połączenia (kanały)
ma oddzielne zestawy buforów.
[w] recv_buf_size
: maksymalny rozmiar każdego pojedynczego bufora w
powyżej bufora. Ta wartość to
zależne od protokołu i skutecznie ogranicza maksymalny rozmiar wiadomości,
z twórcą
[w] flags
: połączenie flag, które określają dodatkowe zachowanie portu
Ta wartość powinna być kombinacją tych wartości:
IPC_PORT_ALLOW_TA_CONNECT
– umożliwia połączenia z innych bezpiecznych aplikacji
IPC_PORT_ALLOW_NS_CONNECT
– zezwala na połączenia z niezabezpieczonego świata.
[retval]: obsługuje połączenie z utworzonym portem, jeśli jest nieujemne lub zawiera określony błąd, jeśli negatywne
Następnie serwer sprawdza listę uchwytów portów w poszukiwaniu połączeń przychodzących.
za pomocą połączenia wait()
. Po otrzymaniu połączenia
żądanie wskazywane przez bit ustawiony w argumencie IPC_HANDLE_POLL_READY
w
pole event
struktury uevent_t
,
serwer powinien wywołać funkcję accept()
, aby zakończyć nawiązanie połączenia i utworzyć
kanał (reprezentowany przez
inny nick), który może być następnie sondowany w poszukiwaniu wiadomości przychodzących.
Accept()
Akceptuje połączenie przychodzące i uzyskuje nick kanału.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[w] handle_id
: uchwyt reprezentujący port, z którym połączył się klient
[out] peer_uuid
: wskaźnik do struktury uuid_t
na
wypełnione identyfikatorem UUID łączącej się aplikacji klienckiej. it
zostanie ustawiona na wszystkie zera, jeśli połączenie pochodzi z niezabezpieczonego świata
[retval]: Obsługa kanału (jeśli nie jest liczbą ujemną), na którym serwer może wymianę wiadomości z klientem (lub w innym przypadku kod błędu).
Interfejs API klienta
Ta sekcja zawiera metody w interfejsie API klienta.
Metody w interfejsie API klienta
połącz()
Inicjuje połączenie z portem określonym przez nazwę.
long connect(const char *path, uint flags);
[w] path
: nazwa portu opublikowanego przez aplikację Trusty
[w] flags
: określa dodatkowe, opcjonalne działanie
[retval]: zgłoszenie kanału, przez który wiadomości mogą być wymieniane z serwer; błąd w przypadku wartości ujemnej
Jeśli nie określono żadnego obiektu flags
(parametr flags
)
jest ustawiona na 0), wywołanie metody connect()
inicjuje połączenie synchroniczne
do określonego portu,
zwraca błąd, jeśli port nie istnieje, i tworzy blok do momentu
W przeciwnym razie serwer akceptuje połączenie.
To działanie można zmienić, określając kombinację dwóch wartości: opisane poniżej:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
– wymusza wartość connect()
wywołanie oczekiwania, jeśli określony port nie istnieje od razu podczas wykonywania,
zamiast awarii natychmiast.
IPC_CONNECT_ASYNC
– jeśli jest ustawiony, inicjuje połączenie asynchroniczne. An
aplikacja musi przeprowadzić sondowanie dla
zwrócony nick (wywołując funkcję wait()
dla
zdarzenie zakończenia połączenia wskazane przez tag IPC_HANDLE_POLL_READY
bit ustawiony w polu zdarzenia struktury uevent_t
przed rozpoczęciem
normalne działanie.
Messaging API
Wywołania interfejsu Messaging API umożliwiają wysyłanie i odczytywanie wiadomości przez nawiązane wcześniej połączenie (kanał). Wywołania interfejsu Messaging API to funkcje są takie same dla serwerów i klientów.
Klient otrzymuje nick dla kanału przez wydanie connect()
i serwer otrzyma nick kanału z wywołania accept()
,
opisane powyżej.
Struktura wiadomości dotyczącej programu Trust
Jak pokazano poniżej, wiadomości wymieniane przez Trusty API mają minimalny , pozostawiając serwerowi i klientowi uzgodnienie semantyki rzeczywista treść:
/* * 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 zawierać jeden lub więcej nieprzylegających buforów reprezentowanych przez
tablica struktur iovec_t
. Trusty zbiera rozproszenie
odczytuje i zapisuje te bloki
za pomocą tablicy iov
. Zawartość buforów, którą można opisać
przez tablicę iov
jest całkowicie dowolna.
Metody w interfejsie Messaging API
send_msg()
Wysyła wiadomość przez określony kanał.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[w] handle
: zgłoszenie kanału, na który ma zostać wysłana wiadomość.
[w] msg
: wskaźnik do pola ipc_msg_t structure
opisującego wiadomość
[retval]: łączna liczba bajtów wysłanych po udanym działaniu; w przeciwnym razie błąd ujemny
Jeśli klient (lub serwer) próbuje przesłać wiadomość przez kanał i
brak miejsca w docelowej kolejce komunikatów równorzędnego, kanał może
przejdzie w stan zablokowania wysyłania (nie powinno to nigdy wystąpić w przypadku prostego
żądania/odpowiedzi, ale może mieć miejsce w bardziej skomplikowanych przypadkach),
co sygnalizuje zwrócenie kodu błędu ERR_NOT_ENOUGH_BUFFER
.
W takim przypadku wywołujący musi poczekać, aż połączenie równorzędne zwolni część
w swojej kolejce odbierania przez pobranie
wiadomości obsługi i wycofania,
wskazuje bit IPC_HANDLE_POLL_SEND_UNBLOCKED
ustawiony w argumencie
pole event
struktury uevent_t
zwrócony przez wywołanie wait()
.
get_msg()
Pobiera metadane o następnej wiadomości w kolejce wiadomości przychodzących
wybranego kanału.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[w] handle
: nick kanału, na który należy pobrać nową wiadomość
[out] msg_info
: struktura informacji o 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;
Do każdej wiadomości jest przypisywany unikalny identyfikator całej grupy oczekujących wiadomości. i wypełniona jest łączna długość każdej wiadomości. Jeśli skonfigurowano i zezwoliło na może być wiele oczekujących (otwartych) wiadomości jednocześnie dla konkretnego kanału.
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd ujemny
read_msg()
Odczytuje treść wiadomości o określonym identyfikatorze, zaczynając od określone przesunięcie.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[w] handle
: nick kanału, z którego ma zostać odczytana wiadomość
[w] msg_id
: identyfikator wiadomości do przeczytania
[w] offset
: przesunięcie w treść 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 będą zapisywane wiadomości przychodzące
dane
[retval]: łączna liczba bajtów zapisanych w buforach msg
w dniu
sukces; w przeciwnym razie błąd ujemny
Metodę read_msg
można wywołać wiele razy, zaczynając od
inny (niekoniecznie
sekwencyjnego).
put_msg()
Wycofuje wiadomość o określonym identyfikatorze.
long put_msg(uint32_t handle, uint32_t msg_id);
[w] handle
: nick kanału, na który została wysłana wiadomość
[in] msg_id
: identyfikator wycofywanej wiadomości
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd ujemny
Po wycofaniu wiadomości nie można uzyskać dostępu do jej treści, a zajęty przez siebie bufor został zwolniony.
Interfejs API deskryptora plików
Interfejs File Descriptor API obejmuje read()
, write()
,
i ioctl()
połączeń. Wszystkie te wywołania mogą działać na wstępnie zdefiniowanym (statycznym) zbiorze plików
deskryptory tradycyjnie reprezentowane przez małe liczby. W bieżącym okresie
implementacji, przestrzeń deskryptora pliku jest oddzielona od uchwytu IPC
kosmosu. Interfejs File Descriptor API w Trusty to
podobny do tradycyjnego interfejsu API opartego na deskryptorach plików.
Domyślnie dostępne są 3 wstępnie zdefiniowane (standardowe i dobrze znane) deskryptory plików:
- 0 – standardowe wejście. Domyślna implementacja standardowych danych wejściowych
fd
jest bezobsługowy (ponieważ zaufane aplikacje nie powinny mieć konsola), więc czytanie, pisanie lub wywoływanieioctl()
wfd
0 powinien zwrócić błądERR_NOT_SUPPORTED
. - 1 – standardowe wyjście. Dane zapisywane na standardowych danych wyjściowych mogą być kierowane (w zależności od
na poziomie debugowania LK) do UART i/lub dziennika pamięci dostępnego w
w zależności od platformy i konfiguracji. Niekrytyczne dzienniki debugowania oraz
wiadomości powinny trafiać do standardowych danych wyjściowych.
read()
iioctl()
są bezobsługowe i powinny zwracać błądERR_NOT_SUPPORTED
. - 2 – błąd standardowy. Dane zapisane w standardowym błędzie powinny być kierowane do UART
lub log pamięci dostępny po niezabezpieczonej stronie, w zależności od platformy
konfiguracji. Zaleca się zapisywanie w standardowym formacie tylko wiadomości o znaczeniu krytycznym
, ponieważ najprawdopodobniej nie zostanie on zakłócony.
read()
i Metodyioctl()
nie są operacjami i powinny zwracać błądERR_NOT_SUPPORTED
.
Mimo że zestaw deskryptorów plików można rozszerzyć, aby zaimplementować więcej
fds
(w celu implementacji rozszerzeń związanych z konkretną platformą), rozszerzanie potrzeb deskryptorów plików
należy zachować ostrożność. Rozszerzanie deskryptorów plików jest często tworzone
i konflikty i zwykle nie jest zalecane.
Metody w interfejsie 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);
[w] fd
: deskryptor pliku, z którego ma nastąpić odczyt
[out] buf
: wskaźnik do bufora, w którym mają być zapisywane dane.
[w] count
: maksymalna liczba bajtów do odczytu
[retval]: zwrócona liczba odczytanych bajtów; w przeciwnym razie błąd ujemny
zapis()
Zapisuje do count
bajtów danych w określonym deskryptorze pliku.
long write(uint32_t fd, void *buf, uint32_t count);
[w] fd
: deskryptor pliku, w którym ma zostać zapisany
[out] buf
: wskaźnik do danych do zapisania
[w] count
: maksymalna liczba bajtów do zapisu
[retval]: zwrócona liczba zapisanych bajtów; w przeciwnym razie błąd ujemny
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, który ma wywoływać metodę ioctl()
[w] cmd
: polecenie ioctl
[w/wy]: args
: wskaźnik do ioctl()
argumentów
Inny interfejs API
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);
[w] clock_id
: zależnie od platformy; ustaw zero, aby użyć wartości domyślnej
[w] flags
: zarezerwowana, powinna wynosić 0
[out] time
: wskaźnik do wartości int64_t
, do której ma być zapisywany bieżący czas
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd ujemny
nanosleep()
Zawiesza wykonywanie aplikacji wywołującej na określony czas i wznawia je po tym czasie.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[w] clock_id
: zarezerwowana, powinna wynosić 0
[w] flags
: zarezerwowana, powinna wynosić 0
[in] sleep_time
: czas snu w nanosekundach
[retval]: NO_ERROR
w przypadku powodzenia; w przeciwnym razie błąd ujemny
Przykład zaufanego serwera aplikacji
Poniższa przykładowa aplikacja pokazuje wykorzystanie powyższych interfejsów API. Przykład tworzy „echo”, która obsługuje wiele połączeń przychodzących przedstawia wywołującemu wszystkie wiadomości, które otrzymuje od klientów, 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 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 10 000 wiadomości asynchronicznie
do „echo” usługa i uchwyty
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 interfejsy API i aplikacje na świecie
Zestaw usług Trusty opublikowanych po stronie zabezpieczeń i oznaczonych symbolem
atrybut IPC_PORT_ALLOW_NS_CONNECT
, są dostępne dla jądra systemu
i programów kosmicznych użytkownika działających na
niezabezpieczonej stronie.
Środowisko wykonawcze po niezabezpieczonej stronie (jądro i przestrzeń użytkownika) jest znacznie różni się od środowiska wykonawczego po stronie zabezpieczeń. Dlatego zamiast jednej biblioteki dla obu środowisk powstały 2 różne zestawy interfejsów API. W jądrze interfejs API klienta jest udostępniany przez sterownik jądra Trusted-ipc i rejestruje węzeł znaków urządzenia, którego można używać przez procesy w przestrzeni użytkownika do komunikowania się z usługami uruchomionymi w bezpiecznym miejscu z boku strony.
Interfejs API klienta Trusty IPC Clientspace
Biblioteka interfejsu API klienta Trusty IPC
to cienka warstwa
węzeł urządzenia fd
.
Program przestrzeni użytkownika rozpoczyna sesję komunikacji
dzwoniąc pod numer tipc_connect()
,
zainicjowanie połączenia z określoną usługą Trusty. Wewnętrznie
wywołanie tipc_connect()
otwiera określony węzeł urządzenia
pobiera deskryptor pliku i wywołuje funkcję TIPC_IOC_CONNECT ioctl()
.
z parametrem argp
wskazującym ciąg znaków zawierający
nazwa usługi, z którą chcesz się połączyć.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Otrzymanego deskryptora pliku można używać tylko do komunikowania się z usługą
dla których została utworzona. Deskryptor pliku powinien być zamknięty do
Wywołuję tipc_close()
, gdy połączenie nie będzie już potrzebne.
Deskryptor pliku uzyskany przez wywołanie tipc_connect()
działa jak typowy znakowy węzeł urządzenia; deskryptor pliku:
- W razie potrzeby można przełączyć w tryb nieblokujący
- Można zapisać w standardowym formacie
write()
zadzwoń, aby wysłać wiadomości na drugą stronę - Może być ankietowana (przy użyciu wywołań
poll()
lubselect()
) Dostępność wiadomości przychodzących w postaci zwykłego deskryptora pliku - Może być odczytywana, aby pobierać wiadomości przychodzące.
Element wywołujący wysyła wiadomość do usługi Trusty przez wykonanie wywołania zapisu dotyczącego
określony element fd
. Wszystkie dane przekazane do powyższego wywołania funkcji write()
jest przekształcany w wiadomość przez zaufany sterownik IPC. Wiadomość to
i dostarcza je na bezpieczną stronę, gdzie dane są przetwarzane przez podsystem IPC
jądro Trusty i przekierowane do odpowiedniego miejsca docelowego oraz dostarczone do aplikacji
pętla zdarzeń jako zdarzenie IPC_HANDLE_POLL_MSG
na konkretnym kanale
uchwytu. W zależności od konkretnego
protokołu w zależności od usługi, usługa Trusty może wysłać jedną lub więcej odpowiedzi
które są dostarczane z powrotem na niezabezpieczoną stronę i umieszczane w
odpowiednia kolejka komunikatów deskryptora pliku kanału, która ma zostać pobrana przez użytkownika
wywołanie aplikacji kosmicznej read()
.
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 zaufanego urządzenia IPC do otwarcia
[w] srv_name
: nazwa opublikowanej usługi powierniczej, z którą ma zostać nawiązane połączenie
[retval]: prawidłowy deskryptor pliku w przypadku powodzenia, -1 w przeciwnym razie.
tipc_close()
Zamyka połączenie z usługą Trusty określoną przez deskryptor pliku.
int tipc_close(int fd);
[w] fd
: deskryptor pliku wcześniej otwarty przez
połączenie tipc_connect()
Interfejs API klienta IPC jądra Trusty
Interfejs Trusty IPC Client API w jądrze jest dostępny dla sterowników jądra. Użytkownik z tym interfejsem API został wdrożony interfejs Trusty IPC API.
Ogólnie typowe użycie tego interfejsu API polega na utworzeniu elementu wywołującego
obiekt struct tipc_chan
za pomocą funkcji tipc_create_channel()
a następnie używając wywołania tipc_chan_connect()
do zainicjowania funkcji
połączenie z usługą Trusty IPC działającą na bezpiecznych
z boku strony. Połączenie ze stroną zdalnym może zostać zakończone przez
wywołuję tipc_chan_shutdown()
, a następnie
tipc_chan_destroy()
w celu wyczyszczenia zasobów.
Po otrzymaniu powiadomienia (przez wywołanie zwrotne handle_event()
)
nawiązano połączenie, rozmówca
następujące:
- Uzyskuje bufor wiadomości przy użyciu wywołania
tipc_chan_get_txbuf_timeout()
- Tworzy wiadomość,
- Umieszcza wiadomość w kolejce przy użyciu interfejsu
tipc_chan_queue_msg()
do usługi Trusty (po stronie zabezpieczeń), do której Kanał jest połączony
Po pomyślnym dodaniu do kolejki rozmówca powinien zapomnieć bufor wiadomości
ponieważ bufor wiadomości wraca do wolnej puli buforów po
na serwerze zdalnym (do późniejszego użycia w przypadku innych wiadomości). Użytkownik
musi wywołać funkcję tipc_chan_put_txbuf()
tylko wtedy, gdy to się nie uda
dodać do kolejki takiego bufora, albo nie jest już potrzebny.
Użytkownik interfejsu API otrzymuje komunikaty ze strony zdalnej przez obsługę
handle_msg()
– wywołanie zwrotne dla powiadomienia (czyli
kontekstu kolejki roboczej Trusty-IPc rx
), która
udostępnia wskaźnik do bufora rx
zawierającego
wiadomości przychodzące do obsługi.
To normalne, że wywołanie zwrotne handle_msg()
implementacja zwróci wskaźnik do prawidłowej wartości struct tipc_msg_buf
.
Może być taki sam jak bufor wiadomości przychodzących, jeśli jest obsługiwany lokalnie.
i nie są już wymagane. Może to być też nowy bufor uzyskany przez
wywołanie tipc_chan_get_rxbuf()
, gdy bufor przychodzących jest w kolejce
w celu dalszego przetworzenia. Należy śledzić odłączony bufor rx
i ostatecznie zwolniona za pomocą wywołania tipc_chan_put_rxbuf()
, gdy:
nie jest już potrzebna.
Metody w interfejsie Jerel Trusty IPC Client API
tipc_create_channel()
Tworzy i konfiguruje instancję zaufanego kanału IPC dla określonego zaufanym urządzeniu 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 adresu IPC, dla którego urządzenie
Utworzenie kanału
[in] ops
: wskaźnik do wartości struct tipc_chan_ops
,
zależne od rozmówcy
wypełnione wywołania zwrotne
[w] cb_arg
: wskaźnik do danych, które zostaną przekazane
do tipc_chan_ops
wywołań zwrotnych
[retval]: wskaźnik do nowo utworzonej instancji
struct tipc_chan
o sukces,
W innym przypadku: ERR_PTR(err)
Ogólnie element wywołujący musi podać dwa wywołania zwrotne, które są asynchroniczne gdy ma miejsce odpowiednie działanie.
Zdarzenie void (*handle_event)(void *cb_arg, int event)
jest wywoływane
powiadamianie rozmówcy o zmianie stanu kanału.
[w] cb_arg
: wskaźnik do danych przekazywanych do
tipc_create_channel()
połączenie
[w] event
: zdarzenie, które może być jedną z tych wartości:
TIPC_CHANNEL_CONNECTED
– oznacza udane połączenie. do zdalnego sterowaniaTIPC_CHANNEL_DISCONNECTED
– wskazuje, że strona zdalna odrzuciła żądanie nowa prośba o połączenie lub prośba o połączenie odłączenie wcześniej połączonego kanałuTIPC_CHANNEL_SHUTDOWN
– oznacza, że strona zdalna wyłącza się, trwałe zakończenie wszystkich połączeń
struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
wywołanie zwrotne dostarcza powiadomienia o tym, że nowa wiadomość została
odebrane w określonym kanale:
- [w]
cb_arg
: wskaźnik do danych przekazywanych do funkcjitipc_create_channel()
połączenie - [in]
mb
: wskaźnik do wartościstruct tipc_msg_buf
opis wiadomości przychodzącej - [retval]: implementacja wywołania zwrotnego powinna zwrócić wskaźnik do
struct tipc_msg_buf
, które mogą być tym samym otrzymanym wskaźnikiem jakomb
, jeśli wiadomość jest obsługiwana lokalnie i nie jest nie jest już wymagane (lub może być nowym buforem uzyskanym przez wywołanietipc_chan_get_rxbuf()
)
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
tipc_create_chan()
połączenie
[in] port
: wskaźnik do ciągu zawierającego
nazwa usługi, z którą chcesz się połączyć
[retval]: 0 – powodzenie, błąd ujemny
Rozmówca jest powiadamiany o nawiązaniu połączenia przez odebranie
handle_event
oddzwonienie.
tipc_chan_shutdown()
Przerywa wcześniej zainicjowane połączenie z usługą Trusty IPC
przez połączenie tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: wskaźnik kanału zwrócony przez
połączenie tipc_create_chan()
tipc_chan_destroy()
Niszczy określony zaufany kanał IPC.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: wskaźnik do kanału zwróconego przez
tipc_create_chan()
połączenie
tipc_chan_get_txbuf_timeout()
Uzyskuje bufor wiadomości, który może być używany do wysyłania danych przez określony kanał. Jeśli bufor nie jest dostępny od razu, rozmówca może zostać zablokowany dla określonego czasu oczekiwania (w milisekundach).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[w] chan
: wskaźnik kanału, do którego należy dodać wiadomość.
[w] chan
: maksymalny czas oczekiwania do
Dostępny jest tx
bufor
[retval]: prawidłowy bufor komunikatu po udanym działaniu,
ERR_PTR(err)
w przypadku błędu
tipc_chan_queue_msg()
Umieszcza w kolejce wiadomość do wysłania przez określony czas Zaufane kanały 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 ma zostać dodana kolejka wiadomości.
[in] mb:
Wskaźnik oznaczający wiadomość do kolejki
(pozyskane przez wywołanie tipc_chan_get_txbuf_timeout()
)
[retval]: 0 – powodzenie, błąd ujemny
tipc_chan_put_txbuf()
Zwalnia określony bufor komunikatu Tx
uzyskane 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 do bufora wiadomości
[w] mb
: wskaźnik do bufora wiadomości w celu opublikowania
[retval]: Brak
tipc_chan_get_rxbuf()
Uzyskuje nowy bufor wiadomości, który może być używany do odbierania wiadomości przez wybranego kanału.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: wskaźnik kanału, do którego należy ten bufor wiadomości
[retval]: prawidłowy bufor komunikatu w przypadku powodzenia, ERR_PTR(err)
w przypadku błędu
tipc_chan_put_rxbuf()
Zwalnia określony bufor wiadomości uzyskany wcześniej przez
tipc_chan_get_rxbuf()
połączenie.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: wskaźnik kanału, do którego należy ten bufor wiadomości
[w] mb
: wskaźnik do bufora wiadomości w celu opublikowania
[retval]: Brak