Dokumentacja Trusty API

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.

Łą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 aplikacji send_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ływanie ioctl() w fd 0 powinien zwrócić błąd ERR_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() i ioctl() są bezobsługowe i powinny zwracać błąd ERR_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 Metody ioctl() nie są operacjami i powinny zwracać błąd ERR_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() lub select()) 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 sterowania
  • TIPC_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łu
  • TIPC_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 funkcji tipc_create_channel() połączenie
  • [in] mb: wskaźnik do wartości struct 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 jako mb, jeśli wiadomość jest obsługiwana lokalnie i nie jest nie jest już wymagane (lub może być nowym buforem uzyskanym przez wywołanie tipc_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