Odniesienie do interfejsu Trusty API

Trusty udostępnia interfejsy API do tworzenia dwóch klas aplikacji/usług:

  • Zaufane aplikacje lub usługi działające na procesorze TEE
  • Normalne/niezaufane aplikacje działające na głównym procesorze i korzystające z usług dostarczanych przez zaufane aplikacje

Interfejs API Trusty ogólnie opisuje system komunikacji międzyprocesowej Trusty (IPC), w tym komunikację ze światem niezabezpieczonym. Oprogramowanie działające na procesorze głównym może używać interfejsów API Trusty do łączenia się z zaufanymi aplikacjami/usługami i wymiany z nimi dowolnych wiadomości, tak jak usługa sieciowa przez IP. Od aplikacji zależy określenie formatu danych i semantyki tych komunikatów przy użyciu protokołu na poziomie aplikacji. Niezawodne dostarczanie komunikatów jest gwarantowane 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 aplikacje Trusty do ujawniania punktów końcowych usługi w postaci nazwanej ścieżki, z którą łączą się klienci. Daje to prosty, oparty na ciągach identyfikator usługi, z którego mogą korzystać klienci. Konwencja nazewnictwa to odwrotne nazewnictwo w stylu DNS, np. com.google.servicename .

Gdy klient łączy się z portem, otrzymuje kanał do interakcji z usługą. Usługa musi zaakceptować połączenie przychodzące, a kiedy to zrobi, również odbiera kanał. Zasadniczo porty są używane do wyszukiwania usług, a następnie komunikacja odbywa się przez parę połączonych kanałów (tj. instancje połączenia na porcie). Gdy klient łączy się z portem, ustanawiane jest symetryczne, dwukierunkowe połączenie. Korzystając z tej ścieżki pełnego dupleksu, klienci i serwery mogą wymieniać dowolne wiadomości, dopóki którakolwiek ze stron nie zdecyduje się na zerwanie połączenia.

Tylko zaufane aplikacje po stronie bezpiecznej lub moduły jądra Trusty mogą tworzyć porty. Aplikacje działające po stronie niezabezpieczonej (w normalnym świecie) mogą łączyć się tylko z usługami opublikowanymi po stronie bezpiecznej.

W zależności od wymagań zaufana aplikacja może być jednocześnie klientem i serwerem. Zaufana aplikacja, która publikuje usługę (jako serwer), może wymagać połączenia z innymi usługami (jako klient).

Obsługa API

Uchwyty są liczbami całkowitymi bez znaku reprezentującymi zasoby, takie jak porty i kanały, podobnie jak deskryptory plików w systemie UNIX. Po utworzeniu uchwytów są one umieszczane w tabeli uchwytów specyficznej dla aplikacji i można się do nich później odwoływać.

Wywołujący może skojarzyć prywatne dane z uchwytem za pomocą metody set_cookie() .

Metody w Handle API

Uchwyty są prawidłowe tylko w kontekście aplikacji. Aplikacja nie powinna przekazywać wartości dojścia do innych aplikacji, chyba że zostanie to wyraźnie określone. Wartość uchwytu powinna być interpretowana tylko przez porównanie go z INVALID_IPC_HANDLE #define, którego aplikacja może użyć jako wskazania, że ​​uchwyt jest nieprawidłowy lub nieustawiony.

Kojarzy prywatne dane dostarczone przez rozmówcę z określonym dojściem.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : Dowolny uchwyt zwrócony przez jedno z wywołań API

[w] cookie : Wskaźnik do dowolnych danych przestrzeni użytkownika w aplikacji Trusty

[retval]: NO_ERROR w przypadku powodzenia, < 0 kod błędu w przeciwnym razie

To wywołanie jest przydatne do obsługi zdarzeń, które występują później po utworzeniu uchwytu. Mechanizm obsługi zdarzeń dostarcza uchwyt i jego plik cookie z powrotem do obsługi zdarzeń.

Na uchwyty można oczekiwać zdarzeń za pomocą wywołania wait() .

czekać()

Czeka na wystąpienie zdarzenia na danym uchwycie przez określony czas.

long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)

[w] handle_id : Dowolny uchwyt zwrócony przez jedno z wywołań API

[out] event : wskaźnik do struktury reprezentującej zdarzenie, które wystąpiło na tym uchwycie

[in] timeout_msecs : Wartość limitu czasu w milisekundach; wartość -1 to nieskończony limit czasu

[retval]: NO_ERROR jeśli ważne zdarzenie wystąpiło w określonym przedziale czasu; ERR_TIMED_OUT jeśli upłynął określony limit czasu, ale nie wystąpiło żadne zdarzenie; < 0 dla innych błędów

Po powodzeniu ( retval == NO_ERROR ), wywołanie wait() wypełnia określoną strukturę uevent_t informacjami o zdarzeniu, które miało miejsce.

typedef struct uevent {
    uint32_t handle; /* handle this event is related to */
    uint32_t event;  /* combination of IPC_HANDLE_POLL_XXX flags */
    void    *cookie; /* cookie associated with this handle */
} uevent_t;

Pole event zawiera kombinację następujących wartości:

enum {
  IPC_HANDLE_POLL_NONE    = 0x0,
  IPC_HANDLE_POLL_READY   = 0x1,
  IPC_HANDLE_POLL_ERROR   = 0x2,
  IPC_HANDLE_POLL_HUP     = 0x4,
  IPC_HANDLE_POLL_MSG     = 0x8,
  IPC_HANDLE_POLL_SEND_UNBLOCKED = 0x10,
  … more values[TBD]
};

IPC_HANDLE_POLL_NONE - w rzeczywistości żadne zdarzenia nie są w toku, dzwonią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 następujący sposób:

  • W przypadku portów ta wartość wskazuje, że istnieje połączenie oczekujące
  • W przypadku kanałów ta wartość wskazuje, że nawiązano połączenie asynchroniczne (patrz connect() )

Następujące wydarzenia dotyczą tylko kanałów:

  • IPC_HANDLE_POLL_HUP - wskazuje, że kanał został zamknięty przez peera
  • IPC_HANDLE_POLL_MSG - wskazuje, że istnieje oczekująca wiadomość dla tego kanału
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - wskazuje, że wywołujący wcześniej zablokowany send może spróbować ponownie wysłać wiadomość (szczegóły w opisie send_msg() )

Program obsługi zdarzeń powinien być przygotowany do obsługi kombinacji określonych zdarzeń, ponieważ jednocześnie może być ustawionych wiele bitów. Na przykład dla kanału możliwe jest posiadanie oczekujących wiadomości i jednoczesne zamknięcie połączenia przez peera.

Większość wydarzeń jest lepka. Utrzymują się one tak długo, jak utrzymuje się podstawowy warunek (na przykład wszystkie oczekujące komunikaty są odbierane i oczekujące żądania połączenia są obsługiwane). Wyjątkiem jest przypadek zdarzenia IPC_HANDLE_POLL_SEND_UNBLOCKED , które jest kasowane po odczycie i aplikacja ma tylko jedną szansę na jego obsłużenie.

Uchwyty można zniszczyć, wywołując metodę close() .

blisko()

Niszczy zasób skojarzony z określonym dojściem i usuwa go z tabeli uchwytów.

long close(uint32_t handle_id);

[w] handle_id : Uchwyt do zniszczenia

[retval]: 0 jeśli sukces; w przeciwnym razie negatywny błąd

Serwer API

Serwer zaczyna od utworzenia jednego lub więcej nazwanych portów reprezentujących jego punkty końcowe usługi. Każdy port jest reprezentowany przez uchwyt.

Metody w API serwera

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)

[in] path : ciąg nazwy portu (zgodnie z powyższym opisem). Ta nazwa powinna być unikalna w całym systemie; próby utworzenia duplikatu nie powiodą się.

[in] num_recv_bufs : Maksymalna liczba buforów, które kanał na tym porcie może wstępnie przydzielić w celu ułatwienia wymiany danych z klientem. Bufory są liczone oddzielnie dla danych idących w obu kierunkach, więc określenie 1 tutaj oznaczałoby, że 1 bufor nadawczy i 1 bufor odbiorczy są wstępnie przydzielone. Ogólnie liczba wymaganych buforów zależy od umowy protokołu wyższego poziomu między klientem a serwerem. Liczba ta może wynosić nawet 1 w przypadku bardzo synchronicznego protokołu (wyślij wiadomość, odbierz odpowiedź przed wysłaniem kolejnej). Ale liczba może być większa, jeśli klient spodziewa się wysłać więcej niż jedną wiadomość, zanim pojawi się odpowiedź (np. jedna wiadomość jako prolog, a druga jako rzeczywiste polecenie). Przydzielone zestawy buforów są na kanał, więc dwa oddzielne połączenia (kanały) miałyby oddzielne zestawy buforów.

[in] recv_buf_size : Maksymalny rozmiar każdego indywidualnego bufora w powyższym zestawie buforów. Ta wartość jest zależna od protokołu i skutecznie ogranicza maksymalny rozmiar wiadomości, którą możesz wymieniać z peerem

[in] flags : kombinacja flag, która określa dodatkowe zachowanie portu

Ta wartość powinna być kombinacją następujących wartości:

IPC_PORT_ALLOW_TA_CONNECT - umożliwia połączenie z innymi bezpiecznymi aplikacjami

IPC_PORT_ALLOW_NS_CONNECT - umożliwia połączenie z niezabezpieczonego świata

[retval]: Uchwyt do portu utworzonego, jeśli nieujemny lub konkretny błąd, jeśli jest ujemny

Następnie serwer odpytuje listę uchwytów portów dla połączeń przychodzących za pomocą wywołania wait() . Po otrzymaniu żądania połączenia wskazanego przez bit IPC_HANDLE_POLL_READY ustawiony w polu event struktury uevent_t , serwer powinien wywołać accept() , aby zakończyć nawiązywanie połączenia i utworzyć kanał (reprezentowany przez inny uchwyt), który może być następnie odpytywany o przychodzące wiadomości .

zaakceptować()

Akceptuje połączenie przychodzące i uzyskuje dojście do kanału.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[w] handle_id : Uchwyt reprezentujący port, do którego połączył się klient

[out] peer_uuid : Wskaźnik do struktury uuud_t do wypełnienia identyfikatorem UUID łączącej się aplikacji klienckiej. Zostanie ustawione na same zera, jeśli połączenie pochodzi z niezabezpieczonego świata

[retval]: Uchwyt do kanału (jeśli nieujemny), na którym serwer może wymieniać wiadomości z klientem (lub kod błędu w przeciwnym razie)

API klienta

Ta sekcja zawiera metody w interfejsie API klienta.

Metody w Client API

łączyć()

Inicjuje połączenie z portem określonym przez nazwę.

long connect(const char *path, uint flags);

[in] path : Nazwa portu publikowana przez aplikację Trusty

[w] flags : Określa dodatkowe, opcjonalne zachowanie

[retval]: Obsługa kanału, przez który wiadomości mogą być wymieniane z serwerem; błąd, jeśli jest ujemny

Jeśli nie określono flags (parametr flags jest ustawiony na 0), wywołanie connect() inicjuje połączenie synchroniczne z określonym portem, które natychmiast zwraca błąd, jeśli port nie istnieje, i tworzy blok, dopóki serwer nie zaakceptuje połączenia w inny sposób .

To zachowanie można zmienić, określając kombinację dwóch wartości, opisanych poniżej:

enum {
IPC_CONNECT_WAIT_FOR_PORT = 0x1,
IPC_CONNECT_ASYNC = 0x2,
};

IPC_CONNECT_WAIT_FOR_PORT — wymusza oczekiwanie wywołania connect() , jeśli określony port nie istnieje natychmiast podczas wykonywania, zamiast natychmiastowej awarii.

IPC_CONNECT_ASYNC — jeśli jest ustawiony, inicjuje połączenie asynchroniczne. Aplikacja musi odpytywać o zwrócony uchwyt (poprzez wywołanie wait() dla zdarzenia zakończenia połączenia wskazanego przez bit IPC_HANDLE_POLL_READY ustawiony w polu zdarzenia struktury uevent_t przed rozpoczęciem normalnego działania.

Interfejs API wiadomości

Wywołania Messaging API umożliwiają wysyłanie i odczytywanie wiadomości przez wcześniej nawiązane połączenie (kanał). Wywołania Messaging API są takie same dla serwerów i klientów.

Klient otrzymuje uchwyt do kanału poprzez wywołanie connect() , a serwer otrzymuje uchwyt kanału z wywołania accept() , opisanego powyżej.

Struktura wiadomości Trusty

Jak pokazano poniżej, wiadomości wymieniane przez Trusty API mają minimalną strukturę, pozostawiając serwerowi i klientowi uzgodnienie semantyki rzeczywistej 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;

Komunikat może składać się z jednego lub więcej nieciągłych buforów reprezentowanych przez tablicę struktur iovec_t . Trusty wykonuje odczyty typu scatter-gather i zapisuje w tych blokach przy użyciu tablicy iov . Zawartość buforów, które można opisać za pomocą tablicy iov , jest całkowicie dowolna.

Metody w Messaging API

wyślij_msg()

Wysyła wiadomość na określonym kanale.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : Uchwyt do kanału, przez który ma zostać wysłana wiadomość

[in] msg : Wskaźnik do ipc_msg_t structure opisującej wiadomość

[retval]: Całkowita liczba bajtów wysłanych w przypadku powodzenia; w przeciwnym razie negatywny błąd

Jeśli klient (lub serwer) próbuje wysłać wiadomość przez kanał, a w docelowej kolejce wiadomości równorzędnej nie ma miejsca, kanał może wejść w stan blokowania wysyłania (nie powinno to mieć miejsca w przypadku prostego synchronicznego protokołu żądania/odpowiedzi ale może się to zdarzyć w bardziej skomplikowanych przypadkach), na co wskazuje zwrócenie kodu błędu ERR_NOT_ENOUGH_BUFFER . W takim przypadku wywołujący musi czekać, aż peer zwolni trochę miejsca w swojej kolejce odbiorczej, pobierając komunikaty obsługi i wycofywania, co jest wskazywane przez bit IPC_HANDLE_POLL_SEND_UNBLOCKED ustawiony w polu event struktury uevent_t zwróconej przez wywołanie wait() .

pobierz_msg()

Pobiera meta-informacje o następnej wiadomości w kolejce wiadomości przychodzących

określonego kanału.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : Uchwyt kanału, na którym musi zostać pobrana nowa wiadomość

[out] msg_info : Struktura informacji o wiadomości opisana w następujący 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 przypisywany jest unikalny identyfikator w całym zestawie oczekujących wiadomości, a całkowita długość każdej wiadomości jest wpisywana. Jeśli jest to skonfigurowane i dozwolone przez protokół, może istnieć wiele oczekujących (otwartych) wiadomości jednocześnie dla określonego kanału.

[retval]: NO_ERROR w przypadku powodzenia; w przeciwnym razie negatywny błąd

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 : Uchwyt kanału, z którego ma zostać odczytana wiadomość

[in] msg_id : ID wiadomości do przeczytania

[in] offset : Przesunięcie do wiadomości, od której należy rozpocząć czytanie

[in] msg : Wskaźnik do struktury ipc_msg_t opisującej zestaw buforów, w których mają być przechowywane dane wiadomości przychodzących

[retval]: Całkowita liczba bajtów przechowywanych w buforach dst po pomyślnym zakończeniu; w przeciwnym razie negatywny błąd

Metodę read_msg można wywoływać wiele razy, zaczynając od innego (niekoniecznie sekwencyjnego) przesunięcia w razie potrzeby.

put_msg()

Wycofuje wiadomość o określonym identyfikatorze.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Uchwyt kanału, na który dotarła wiadomość

[w] msg_id : ID wiadomości, która jest wycofywana

[retval]: NO_ERROR w przypadku powodzenia; w przeciwnym razie negatywny błąd

Nie można uzyskać dostępu do treści wiadomości po wycofaniu wiadomości i zwolnieniu zajmowanego przez nią bufora.

API deskryptora plików

Interfejs API deskryptora plików zawiera wywołania read() , write() i ioctl() . Wszystkie te wywołania mogą działać na predefiniowanym (statycznym) zestawie deskryptorów plików tradycyjnie reprezentowanych przez małe liczby. W obecnej implementacji przestrzeń deskryptora pliku jest oddzielona od przestrzeni uchwytów IPC. Interfejs API deskryptora plików w Trusty jest podobny do tradycyjnego interfejsu API opartego na deskryptorze plików.

Domyślnie istnieją 3 predefiniowane (standardowe i dobrze znane) deskryptory plików:

  • 0 - wejście standardowe. Domyślna implementacja standardowego wejścia fd to no-op (ponieważ zaufane aplikacje nie powinny mieć interaktywnej konsoli), więc czytanie, pisanie lub wywoływanie ioctl() na fd 0 powinno zwrócić błąd ERR_NOT_SUPPORTED .
  • 1 - wyjście standardowe. Dane zapisywane na standardowym wyjściu mogą być kierowane (w zależności od poziomu debugowania LK) do UART i/lub dziennika pamięci dostępnego po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Niekrytyczne dzienniki debugowania i komunikaty powinny być wysyłane na standardowe wyjście. Metody read() i ioctl() nie wymagają operacji i powinny zwrócić błąd ERR_NOT_SUPPORTED .
  • 2 - błąd standardowy. Dane zapisane do standardowego błędu powinny być kierowane do dziennika UART lub pamięci dostępnej po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Zaleca się zapisywanie tylko krytycznych wiadomości do standardowego błędu, ponieważ ten strumień jest bardzo prawdopodobny. Metody read() i ioctl() nie wymagają operacji i powinny zwrócić błąd ERR_NOT_SUPPORTED .

Mimo że ten zestaw deskryptorów plików można rozszerzyć, aby zaimplementować więcej plików fds (w celu zaimplementowania rozszerzeń specyficznych dla platformy), rozszerzanie deskryptorów plików należy wykonywać ostrożnie. Rozszerzanie deskryptorów plików może powodować konflikty i generalnie nie jest zalecane.

Metody w API deskryptorów plików

czytać()

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, z którego należy czytać

[out] buf : Wskaźnik do bufora, w którym mają być przechowywane dane

[in] count : Maksymalna liczba bajtów do odczytania

[retval]: Zwrócona liczba odczytanych bajtów; w przeciwnym razie negatywny błąd

pisać()

Zapisuje do count bajtów danych do określonego deskryptora pliku.

long write(uint32_t fd, void *buf, uint32_t count);

[in] fd : Deskryptor pliku, do którego należy pisać

[out] buf : Wskaźnik do danych do zapisu

[in] count : Maksymalna liczba bajtów do zapisania

[retval]: Zwrócona liczba zapisanych bajtów; w przeciwnym razie negatywny błąd

ioctl()

Wywołuje określone polecenie ioctl dla podanego deskryptora pliku.

long ioctl(uint32_t fd, uint32_t cmd, void *args);

[in] fd : Deskryptor pliku, na którym należy wywołać ioctl()

[in] cmd : Polecenie ioctl

[in/out] args : Wskaźnik do argumentów ioctl()

Różne API

Metody w różnych API

uzyskać czas()

Zwraca bieżący czas systemowy (w nanosekundach).

long gettime(uint32_t clock_id, uint32_t flags, uint64_t *time);

[w] clock_id : zależny od platformy; przekaż zero jako domyślne

[in] flags : Zarezerwowane, powinno wynosić zero

[in] time : Wskaźnik do wartości int64_t , w której ma być przechowywany aktualny czas

[retval]: NO_ERROR w przypadku powodzenia; w przeciwnym razie negatywny błąd

nanosleep()

Wstrzymuje wykonywanie aplikacji wywołującej na określony czas i wznawia ją po tym okresie.

long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)

[in] clock_id : Zarezerwowane, powinno wynosić zero

[in] flags : Zarezerwowane, powinno wynosić zero

[in] sleep_time : czas snu w nanosekundach

[retval]: NO_ERROR w przypadku powodzenia; w przeciwnym razie negatywny błąd

Przykład zaufanego serwera aplikacji

Poniższa przykładowa aplikacja pokazuje użycie powyższych interfejsów API. Przykład tworzy usługę „echa”, która obsługuje wiele połączeń przychodzących i odzwierciedla z powrotem do osoby wywołującej wszystkie komunikaty, które otrzymuje 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 application entry point
 */
int main(void) {
  int rc;
  handle_t port;

  /* Initialize service */
  rc = port_create(srv_name, 1, MAX_ECHO_MSG_SIZE,
    IPC_PORT_ALLOW_NS_CONNECT |
    IPC_PORT_ALLOW_TA_CONNECT);
  if (rc < 0) {
    TLOGE("Failed (%d) to create port %s\n",
      rc, srv_name);
    abort();
  }
  port = (handle_t) rc;

  /* enter main event loop */
  while (true) {
    uevent_t ev;

    ev.handle = INVALID_IPC_HANDLE;
    ev.event = 0;
    ev.cookie = NULL;

    /* wait forever */
    rc = wait(port, &ev, INFINITE_TIME);
    if (rc == NO_ERROR) {
      /* got an event */
      handle_port_event(&ev);
    } else {
      TLOGE("wait returned (%d)\n", rc);
      abort();
    }
  }
  return 0;
}

Metoda run_end_to_end_msg_test() wysyła asynchronicznie 10 000 komunikatów do usługi „echo” i obsługuje odpowiedzi.

static int run_echo_test(void)
{
  int rc;
  handle_t chan;
  uevent_t uevt;
  uint8_t tx_buf[64];
  uint8_t rx_buf[64];
  ipc_msg_info_t inf;
  ipc_msg_t   tx_msg;
  iovec_t     tx_iov;
  ipc_msg_t   rx_msg;
  iovec_t     rx_iov;

  /* prepare tx message buffer */
  tx_iov.base = tx_buf;
  tx_iov.len  = sizeof(tx_buf);
  tx_msg.num_iov = 1;
  tx_msg.iov     = &tx_iov;
  tx_msg.num_handles = 0;
  tx_msg.handles = NULL;

  memset (tx_buf, 0x55, sizeof(tx_buf));

  /* prepare rx message buffer */
  rx_iov.base = rx_buf;
  rx_iov.len  = sizeof(rx_buf);
  rx_msg.num_iov = 1;
  rx_msg.iov     = &rx_iov;
  rx_msg.num_handles = 0;
  rx_msg.handles = NULL;

  /* open connection to echo service */
  rc = sync_connect(srv_name, 1000);
  if(rc < 0)
    return rc;

  /* got channel */
  chan = (handle_t)rc;

  /* send/receive 10000 messages asynchronously. */
  uint tx_cnt = 10000;
  uint rx_cnt = 10000;

  while (tx_cnt || rx_cnt) {
    /* send messages until all buffers are full */
while (tx_cnt) {
    rc = send_msg(chan, &tx_msg);
      if (rc == ERR_NOT_ENOUGH_BUFFER)
      break;  /* no more space */
    if (rc != 64) {
      if (rc > 0) {
        /* incomplete send */
        rc = ERR_NOT_VALID;
}
      goto abort_test;
}
    tx_cnt--;
  }

  /* wait for reply msg or room */
  rc = wait(chan, &uevt, 1000);
  if (rc != NO_ERROR)
    goto abort_test;

  /* drain all messages */
  while (rx_cnt) {
    /* get a reply */
      rc = get_msg(chan, &inf);
    if (rc == ERR_NO_MSG)
        break;  /* no more messages  */
  if (rc != NO_ERROR)
goto abort_test;

  /* read reply data */
    rc = read_msg(chan, inf.id, 0, &rx_msg);
  if (rc != 64) {
    /* unexpected reply length */
    rc = ERR_NOT_VALID;
    goto abort_test;
}

  /* discard reply */
  rc = put_msg(chan, inf.id);
  if (rc != NO_ERROR)
    goto abort_test;
  rx_cnt--;
  }
}

abort_test:
  close(chan);
  return rc;
}

Niezabezpieczone światowe interfejsy API i aplikacje

Zestaw usług Trusty, opublikowany po stronie bezpiecznej i oznaczony atrybutem IPC_PORT_ALLOW_NS_CONNECT , jest dostępny dla programów jądra i przestrzeni użytkownika działających po stronie niezabezpieczonej.

Środowisko wykonawcze po stronie niezabezpieczonej (jądro i przestrzeń użytkownika) znacznie różni się od środowiska wykonawczego po stronie bezpiecznej. Dlatego zamiast jednej biblioteki dla obu środowisk istnieją dwa różne zestawy interfejsów API. W jądrze Client API jest dostarczany przez sterownik jądra trusty-ipc i rejestruje węzeł urządzenia znakowego, który może być używany przez procesy przestrzeni użytkownika do komunikowania się z usługami działającymi po bezpiecznej stronie.

Przestrzeń użytkownika Trusty IPC Client API

Biblioteka Trusty IPC Client API w przestrzeni użytkownika jest cienką warstwą na wierzchu węzła urządzenia fd .

Program w przestrzeni użytkownika rozpoczyna sesję komunikacyjną, wywołując tipc_connect() , inicjując połączenie z określoną usługą Trusty. Wewnętrznie tipc_connect() otwiera określony węzeł urządzenia w celu uzyskania deskryptora pliku i wywołuje TIPC_IOC_CONNECT ioctl() z parametrem argp wskazującym na ciąg znaków zawierający nazwę usługi, z którą należy się połączyć.

#define TIPC_IOC_MAGIC  'r'
#define TIPC_IOC_CONNECT  _IOW(TIPC_IOC_MAGIC, 0x80, char *)

Otrzymany deskryptor pliku może być używany tylko do komunikacji z usługą, dla której został utworzony. Deskryptor pliku należy zamknąć, wywołując tipc_close() , gdy połączenie nie jest już wymagane.

Deskryptor pliku uzyskany przez tipc_connect() zachowuje się jak typowy węzeł urządzenia znakowego; deskryptor pliku:

  • W razie potrzeby można przełączyć na tryb nieblokujący
  • Można napisać przy użyciu standardowego wywołania write() do wysyłania wiadomości na drugą stronę
  • Może być odpytywany (za pomocą wywołań poll() lub wywołań select() ) pod kątem dostępności przychodzących wiadomości jako zwykłego deskryptora pliku
  • Można odczytać, aby pobrać przychodzące wiadomości

Wywołujący wysyła wiadomość do usługi Trusty, wykonując wywołanie zapisu dla określonego fd . Wszystkie dane przekazane do powyższego wywołania write() są przekształcane w komunikat przez sterownik trusty-ipc. Komunikat jest dostarczany do bezpiecznej strony, gdzie dane są obsługiwane przez podsystem IPC w jądrze Trusty i kierowane do odpowiedniego miejsca docelowego oraz dostarczane do pętli zdarzeń aplikacji jako zdarzenie IPC_HANDLE_POLL_MSG w określonym uchwycie kanału. W zależności od konkretnego protokołu specyficznego dla usługi, usługa Trusty może wysłać jedną lub więcej wiadomości zwrotnych, które są dostarczane z powrotem do strony niezabezpieczonej i umieszczane w kolejce wiadomości odpowiedniego deskryptora pliku kanału do pobrania przez aplikację w przestrzeni użytkownika read() zadzwoń.

tipc_connect()

Otwiera określony węzeł urządzenia tipc i inicjuje połączenie z określoną usługą Trusty.

int tipc_connect(const char *dev_name, const char *srv_name);

[in] dev_name : Ścieżka do węzła urządzenia Trusty IPC do otwarcia

[in] srv_name : Nazwa opublikowanej usługi Trusty, z którą należy się połączyć

[retval]: poprawny deskryptor pliku w przypadku powodzenia, w przeciwnym razie -1.

tipc_close()

Zamyka połączenie z usługą Trusty określoną przez deskryptor pliku.

int tipc_close(int fd);

[in] fd : Deskryptor pliku wcześniej otwarty przez tipc_connect()

Kernel Trusty IPC Client API

Interfejs API Trusty IPC Client API jest dostępny dla sterowników jądra. Interfejs API Trusty IPC w przestrzeni użytkownika jest zaimplementowany na tym interfejsie.

Ogólnie rzecz biorąc, typowe użycie tego API polega na tym, że wywołujący tworzy obiekt struct tipc_chan za pomocą funkcji tipc_create_channel() , a następnie używa tipc_chan_connect() w celu zainicjowania połączenia z usługą Trusty IPC działającą po bezpiecznej stronie. Połączenie ze stroną zdalną można zakończyć, wywołując tipc_chan_shutdown() , a następnie tipc_chan_destroy() w celu oczyszczenia zasobów.

Po otrzymaniu powiadomienia (poprzez wywołanie zwrotne handle_event() ), że połączenie zostało pomyślnie nawiązane, wywołujący wykonuje następujące czynności:

  • Uzyskuje bufor wiadomości za pomocą tipc_chan_get_txbuf_timeout()
  • Tworzy wiadomość i
  • Kolejkuje wiadomość przy użyciu metody tipc_chan_queue_msg() w celu dostarczenia do usługi Trusty (po bezpiecznej stronie), z którą kanał jest połączony

Po pomyślnym kolejkowaniu wywołujący powinien zapomnieć o buforze wiadomości, ponieważ bufor wiadomości w końcu wraca do wolnej puli buforów po przetworzeniu przez stronę zdalną (do ponownego wykorzystania później dla innych wiadomości). Użytkownik musi tylko wywołać tipc_chan_put_txbuf() , jeśli nie uda się zakolejkować takiego bufora lub nie jest już wymagany.

Użytkownik API odbiera komunikaty ze strony zdalnej, obsługując wywołanie zwrotne powiadomień handle_msg() (które jest wywoływane w kontekście kolejki roboczej trusty-ipc rx ), które dostarcza wskaźnik do bufora rx zawierającego przychodzący komunikat do obsłużenia.

Oczekuje się, że implementacja wywołania zwrotnego handle_msg() zwróci wskaźnik do poprawnej struct tipc_msg_buf . Może być taki sam jak bufor wiadomości przychodzących, jeśli jest obsługiwany lokalnie i nie jest już wymagany. Alternatywnie, może to być nowy bufor uzyskany przez tipc_chan_get_rxbuf() , jeśli bufor przychodzący jest umieszczony w kolejce do dalszego przetwarzania. Odłączony bufor rx musi być śledzony i ostatecznie zwolniony za pomocą tipc_chan_put_rxbuf() , gdy nie jest już potrzebny.

Metody w Kernel Trusty IPC Client API

tipc_create_channel()

Tworzy i konfiguruje wystąpienie kanału Trusty IPC dla konkretnego urządzenia Trusty-IPC.

struct tipc_chan *tipc_create_channel(struct device *dev,
                          const struct tipc_chan_ops *ops,
                              void *cb_arg);

[in] dev : Wskaźnik do zaufanego IPC, dla którego tworzony jest kanał urządzenia

[in] ops : Wskaźnik do struct tipc_chan_ops , z wypełnionymi wywołaniami zwrotnymi specyficznymi dla rozmówcy

[w] cb_arg : Wskaźnik do danych, które zostaną przekazane do wywołań zwrotnych tipc_chan_ops

[retval]: Wskaźnik do nowo utworzonej instancji struct tipc_chan w przypadku powodzenia, w przeciwnym razie ERR_PTR(err)

Ogólnie rzecz biorąc, wywołujący musi zapewnić dwa wywołania zwrotne, które są wywoływane asynchronicznie, gdy występuje odpowiednie działanie.

void (*handle_event)(void *cb_arg, int event) jest wywoływane w celu powiadomienia wywołującego o zmianie stanu kanału.

[w] cb_arg : Wskaźnik do danych przekazanych do tipc_create_channel()

[w] event : zdarzenie, które może mieć jedną z następujących wartości:

  • TIPC_CHANNEL_CONNECTED - wskazuje pomyślne połączenie ze stroną zdalną
  • TIPC_CHANNEL_DISCONNECTED - wskazuje, że strona zdalna odmówiła nowego żądania połączenia lub zażądała rozłączenia dla poprzednio połączonego kanału
  • TIPC_CHANNEL_SHUTDOWN - wskazuje, że strona zdalna się wyłącza, trwale zrywając wszystkie połączenia

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) jest wywoływane, aby dostarczyć powiadomienie, że nowa wiadomość została odebrana przez określony kanał:

  • [w] cb_arg : Wskaźnik do danych przekazanych do tipc_create_channel()
  • [in] mb : Wskaźnik do struct tipc_msg_buf opisującej przychodzącą wiadomość
  • [retval]: Oczekuje się, że implementacja wywołania zwrotnego zwróci wskaźnik do struct tipc_msg_buf , która 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 tipc_chan_get_rxbuf() wywołanie)

tipc_chan_connect()

Inicjuje połączenie z określoną usługą Trusty IPC.

int tipc_chan_connect(struct tipc_chan *chan, const char *port);

[in] chan : Wskaźnik do kanału zwróconego przez tipc_create_chan()

[in] port : Wskaźnik do ciągu zawierającego nazwę usługi, z którą chcesz się połączyć

[retval]: 0 w przypadku powodzenia, w przeciwnym razie błąd ujemny

Dzwoniący jest powiadamiany o nawiązaniu połączenia poprzez odebranie wywołania zwrotnego handle_event .

tipc_chan_shutdown()

Przerywa połączenie z usługą Trusty IPC, które zostało wcześniej zainicjowane przez tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : Wskaźnik do kanału zwróconego przez tipc_create_chan()

tipc_chan_destroy()

Niszczy określony kanał Trusty IPC.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : Wskaźnik do kanału zwróconego przez tipc_create_chan()

tipc_chan_get_txbuf_timeout()

Uzyskuje bufor komunikatów, którego można użyć do wysyłania danych przez określony kanał. Jeśli bufor nie jest natychmiast dostępny, wywołujący może zostać zablokowany na określony czas (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 być zakolejkowana wiadomość

[in] chan : Maksymalny limit czasu oczekiwania, aż bufor tx stanie się dostępny

[retval]: Prawidłowy bufor wiadomości w przypadku powodzenia, ERR_PTR(err) w przypadku błędu

tipc_chan_queue_msg()

Kolejkuje wiadomość do wysłania przez określone kanały Trusty IPC.

int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);

[in] chan : Wskaźnik do kanału, do którego ma zostać zakolejkowana wiadomość

[w] mb: Wskaźnik do wiadomości do kolejki (uzyskany przez tipc_chan_get_txbuf_timeout() )

[retval]: 0 w przypadku powodzenia, w przeciwnym razie błąd ujemny

tipc_chan_put_txbuf()

Zwalnia określony bufor komunikatów Tx wcześniej uzyskany przez 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

[wypłata]: Brak

tipc_chan_get_rxbuf()

Uzyskuje nowy bufor wiadomości, którego można użyć do odbierania wiadomości przez określony kanał.

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 uzyskany wcześniej przez 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

[wypłata]: Brak