Dokumentacja Trusty API

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.

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 opisie send_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ływanie ioctl() w wartości fd 0 powinny zwracać błąd ERR_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() i ioctl() nie wykonują żadnej operacji i powinny zwracać błąd ERR_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() i ioctl() nie wykonują żadnej operacji i powinny zwracać błąd ERR_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() lub select()) 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łania tipc_create_channel()
  • [in] mb: wskaźnik do struct tipc_msg_bufopisują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 parametr mb, jeśli wiadomość jest obsługiwana lokalnie i nie jest już wymagana (lub może to być nowy bufor uzyskany przez wywołanie tipc_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