Odniesienie do interfejsu Trusty API

Trusty udostępnia interfejsy API umożliwiające tworzenie dwóch klas aplikacji/usług:

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

Trusty API ogólnie opisuje system komunikacji międzyprocesowej Trusty (IPC), w tym komunikację ze światem niezabezpieczonym. Oprogramowanie działające na głównym procesorze może wykorzystywać interfejsy Trusty API do łączenia się z zaufanymi aplikacjami/usługami i wymieniania z nimi dowolnych wiadomości, podobnie jak usługa sieciowa za pośrednictwem protokołu IP. Do aplikacji należy określenie formatu danych i semantyki tych wiadomości przy użyciu protokołu na poziomie aplikacji. Niezawodne dostarczanie komunikatów gwarantuje bazowa infrastruktura Trusty (w postaci sterowników działających na głównym procesorze), a komunikacja jest całkowicie asynchroniczna.

Porty i kanały

Porty są używane przez aplikacje Trusty do udostępniania punktów końcowych usług w formie nazwanej ścieżki, z którą łączą się klienci. Daje to prosty identyfikator usługi oparty na ciągach znaków, z którego mogą korzystać klienci. Konwencja nazewnictwa to nazewnictwo w odwrotnym stylu DNS, np. com.google.servicename .

Gdy klient łączy się z portem, otrzymuje kanał umożliwiający interakcję z usługą. Usługa musi zaakceptować połączenie przychodzące, a kiedy to zrobi, również otrzyma kanał. Zasadniczo porty służą do wyszukiwania usług, a następnie komunikacja odbywa się za pośrednictwem pary połączonych kanałów (tj. instancji połączenia na porcie). Kiedy klient łączy się z portem, ustanawiane jest symetryczne, dwukierunkowe połączenie. Korzystając ze ścieżki pełnego dupleksu, klienci i serwery mogą wymieniać dowolne komunikaty, dopóki którakolwiek ze stron nie zdecyduje się przerwać połączenia.

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

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

Obsługa API

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

Osoba wywołująca może powiązać prywatne dane z uchwytem za pomocą metody set_cookie() .

Metody w interfejsie API obsługi

Uchwyty obowiązują tylko w kontekście aplikacji. Aplikacja nie powinna przekazywać wartości uchwytu innym aplikacjom, chyba że zostało to wyraźnie określone. Wartość uchwytu należy interpretować jedynie poprzez porównanie jej z INVALID_IPC_HANDLE #define, której aplikacja może użyć jako wskazania, że ​​uchwyt jest nieprawidłowy lub nieustawiony.

Kojarzy prywatne dane dostarczone przez osobę wywołującą z określonym uchwytem.

long set_cookie(uint32_t handle, void *cookie)

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

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

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

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

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

Czekać()

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

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

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

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

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

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

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

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

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

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

IPC_HANDLE_POLL_NONE - w rzeczywistości żadne zdarzenia nie są w toku, wywołujący powinien ponownie rozpocząć oczekiwanie

IPC_HANDLE_POLL_ERROR - wystąpił nieokreślony błąd wewnętrzny

IPC_HANDLE_POLL_READY - zależy od typu uchwytu, w następujący sposób:

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

Poniższe zdarzenia dotyczą tylko kanałów:

  • IPC_HANDLE_POLL_HUP - wskazuje, że kanał został zamknięty przez partnera
  • IPC_HANDLE_POLL_MSG - wskazuje, że dla tego kanału istnieje oczekująca wiadomość
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - wskazuje, że dzwoniący, który wcześniej zablokował wysyłanie, może spróbować ponownie wysłać wiadomość (szczegóły w opisie funkcji send_msg() )

Procedura obsługi zdarzeń powinna być przygotowana do obsługi kombinacji określonych zdarzeń, ponieważ jednocześnie może być ustawionych wiele bitów. Na przykład w przypadku kanału możliwe jest jednoczesne zamknięcie oczekujących wiadomości i połączenia przez partnera równorzędnego.

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

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

zamknąć()

Niszczy zasób powiązany z określonym uchwytem i usuwa go z tabeli uchwytów.

long close(uint32_t handle_id);

[w] handle_id : Uchwyt do zniszczenia

[retval]: 0, jeśli się powiedzie; w przeciwnym razie błąd negatywny

API serwera

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

Metody w API serwera

port_utwórz()

Tworzy nazwany port usługi.

long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size,
uint32_t flags)

[in] path : Ciąg nazwy portu (jak opisano powyżej). Nazwa ta powinna być unikalna w całym systemie; próby utworzenia duplikatu nie powiodą się.

[in] num_recv_bufs : Maksymalna liczba buforów, które kanał na tym porcie może wstępnie przydzielić, aby ułatwić wymianę danych z klientem. Bufory są liczone oddzielnie dla danych przesyłanych w obu kierunkach, więc podanie tutaj wartości 1 oznaczałoby, że wstępnie przydzielono 1 bufor wysyłania i 1 odbierania. Ogólnie rzecz biorąc, liczba wymaganych buforów zależy od umowy protokołu wyższego poziomu między klientem a serwerem. Liczba może wynosić zaledwie 1 w przypadku protokołu bardzo synchronicznego (wyślij wiadomość, odbierz odpowiedź przed wysłaniem kolejnej). Jednak liczba ta może być większa, jeśli klient spodziewa się wysłać więcej niż jedną wiadomość, zanim pojawi się odpowiedź (np. jedna wiadomość jako prolog, a druga jako właściwe polecenie). Przydzielone zestawy buforów są przypisane do każdego kanału, więc dwa oddzielne połączenia (kanały) będą miały oddzielne zestawy buforów.

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

[in] flags : Kombinacja flag określająca dodatkowe zachowanie portu

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

IPC_PORT_ALLOW_TA_CONNECT - umożliwia połączenie z innych bezpiecznych aplikacji

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

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

Następnie serwer odpytuje listę uchwytów portów dla połączeń przychodzących, używając wywołania wait() . Po otrzymaniu żądania połączenia wskazywanego przez bit IPC_HANDLE_POLL_READY ustawiony w polu event struktury uevent_t , serwer powinien wywołać funkcję accept() , aby zakończyć nawiązywanie połączenia i utworzyć kanał (reprezentowany przez inny uchwyt), który będzie następnie mógł być odpytywany pod kątem przychodzących wiadomości .

zaakceptować()

Akceptuje połączenie przychodzące i uzyskuje uchwyt do kanału.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

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

[na zewnątrz] peer_uuid : Wskaźnik do struktury uuid_t , która ma zostać wypełniona identyfikatorem UUID łączącej się aplikacji klienckiej. Jeśli połączenie pochodzi z niezabezpieczonego świata, zostanie ustawione na same zera

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

API klienta

Ta sekcja zawiera metody w Client API.

Metody w Client API

łączyć()

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

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

[w] path : Nazwa portu opublikowana przez aplikację Trusty

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

[retval]: Uchwyt do kanału, przez który można wymieniać wiadomości z serwerem; błąd, jeśli jest ujemny

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

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

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

IPC_CONNECT_WAIT_FOR_PORT - wymusza oczekiwanie wywołania connect() , jeśli określony port nie istnieje od razu podczas wykonywania, zamiast natychmiastowego niepowodzenia.

IPC_CONNECT_ASYNC - jeśli ustawione, inicjuje połączenie asynchroniczne. Aplikacja musi odpytać o zwrócony uchwyt (poprzez wywołanie funkcji wait() w celu uzyskania zdarzenia zakończenia połączenia wskazywanego przez bit IPC_HANDLE_POLL_READY ustawiony w polu zdarzenia struktury uevent_t przed rozpoczęciem normalnej pracy).

Interfejs API przesyłania wiadomości

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

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

Struktura wiadomości Trusty

Jak pokazano poniżej, wiadomości wymieniane przez Trusty API mają minimalną strukturę, pozostawiając serwerowi i klientowi uzgodnienie semantyki rzeczywistej treści:

/*
 *  IPC message
 */
typedef struct iovec {
        void   *base;
        size_t  len;
} iovec_t;

typedef struct ipc_msg {
        uint     num_iov; /* number of iovs in this message */
        iovec_t  *iov;    /* pointer to iov array */

        uint     num_handles; /* reserved, currently not supported */
        handle_t *handles;    /* reserved, currently not supported */
} ipc_msg_t;

Komunikat może składać się z jednego lub większej liczby nieciągłych buforów reprezentowanych przez tablicę struktur iovec_t . Trusty dokonuje odczytu i zapisu w tych blokach metodą scatter-gather przy użyciu tablicy iov . Zawartość buforów, które można opisać tablicą iov , jest całkowicie dowolna.

Metody w interfejsie API przesyłania wiadomości

wyślij_msg()

Wysyła wiadomość przez określony kanał.

long send_msg(uint32_t handle, ipc_msg_t *msg);

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

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

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

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

get_msg()

Pobiera metainformacje o następnej wiadomości w kolejce wiadomości przychodzących

określonego kanału.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

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

[out] msg_info : Struktura informacji wiadomości opisana poniżej:

typedef struct ipc_msg_info {
        size_t    len;  /* total message length */
        uint32_t  id;   /* message id */
} ipc_msg_info_t;

Każdej wiadomości przypisany jest unikalny identyfikator w zestawie zaległych wiadomości i wypełniana jest całkowita długość każdej wiadomości. Jeśli protokół jest skonfigurowany i pozwala na to, dla określonego kanału może istnieć wiele zaległych (otwartych) wiadomości jednocześnie.

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

przeczytaj_msg()

Odczytuje treść komunikatu o określonym identyfikatorze, zaczynając od określonego przesunięcia.

long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t
*msg);

[w] handle : Uchwyt kanału, z którego ma zostać odczytana wiadomość

[w] msg_id : Identyfikator wiadomości do przeczytania

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

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

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

Metodę read_msg można wywołać wielokrotnie, zaczynając od innego (niekoniecznie sekwencyjnego) przesunięcia, jeśli zajdzie taka potrzeba.

put_msg()

Wycofuje wiadomość o określonym identyfikatorze.

long put_msg(uint32_t handle, uint32_t msg_id);

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

[w] msg_id : Identyfikator wycofywanej wiadomości

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

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

API deskryptora pliku

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

Domyślnie dostępne są 3 predefiniowane (standardowe i dobrze znane) deskryptory plików:

  • 0 - wejście standardowe. Domyślna implementacja standardowego wejścia fd nie daje żadnego efektu (ponieważ nie oczekuje się, że zaufane aplikacje będą miały interaktywną konsolę), więc czytanie, zapisywanie lub wywoływanie ioctl() na fd 0 powinno zwrócić błąd ERR_NOT_SUPPORTED .
  • 1 - wyjście standardowe. Dane zapisane na standardowe wyjście mogą być kierowane (w zależności od poziomu debugowania LK) do UART i/lub dziennika pamięci dostępnego po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Niekrytyczne dzienniki debugowania i komunikaty powinny trafiać na standardowe wyjście. Metody read() i ioctl() nie dają żadnego efektu i powinny zwrócić błąd ERR_NOT_SUPPORTED .
  • 2 - błąd standardowy. Dane zapisane na błąd standardowy powinny być kierowane do UART lub dziennika pamięci dostępnego po stronie niezabezpieczonej, w zależności od platformy i konfiguracji. Zaleca się zapisywanie tylko krytycznych komunikatów do standardowego błędu, ponieważ z dużym prawdopodobieństwem strumień ten nie będzie ograniczany. Metody read() i ioctl() nie dają żadnego efektu i powinny zwrócić błąd ERR_NOT_SUPPORTED .

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

Metody w interfejsie API deskryptora pliku

Czytać()

Próbuje odczytać count bajtów danych z określonego deskryptora pliku.

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

[w] fd : Deskryptor pliku, z którego należy czytać

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

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

[retval]: Zwracana liczba przeczytanych bajtów; w przeciwnym razie błąd negatywny

pisać()

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

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

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

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

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

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

ioctl()

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

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

[w] fd : Deskryptor pliku, na którym można wywołać ioctl()

[w] cmd : Polecenie ioctl

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

Różne API

Metody w Miscellaneous API

uzyskać czas()

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

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

[w] clock_id : Zależne od platformy; przekazać zero jako domyślne

[w] flags : zarezerwowane, powinno wynosić zero

[out] time : Wskaźnik do wartości int64_t , w której ma być przechowywany bieżący czas

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

nanosen()

Zawiesza wykonywanie aplikacji wywołującej na określony czas i wznawia je po upływie tego czasu.

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

[w] clock_id : Zarezerwowane, powinno wynosić zero

[w] flags : zarezerwowane, powinno wynosić zero

[w] sleep_time : Czas snu w nanosekundach

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

Przykład zaufanego serwera aplikacji

Poniższa przykładowa aplikacja przedstawia użycie powyższych interfejsów API. Przykład tworzy usługę „echo”, która obsługuje wiele połączeń przychodzących i odzwierciedla wywołującemu wszystkie wiadomości otrzymane od klientów, pochodzące ze strony bezpiecznej lub niezabezpieczonej.

#include <uapi/err.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trusty_ipc.h>
#define LOG_TAG "echo_srv"
#define TLOGE(fmt, ...) \
    fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ##__VA_ARGS__)

# define MAX_ECHO_MSG_SIZE 64

static
const char * srv_name = "com.android.echo.srv.echo";

static uint8_t msg_buf[MAX_ECHO_MSG_SIZE];

/*
 *  Message handler
 */
static int handle_msg(handle_t chan) {
  int rc;
  struct iovec iov;
  ipc_msg_t msg;
  ipc_msg_info_t msg_inf;

  iov.iov_base = msg_buf;
  iov.iov_len = sizeof(msg_buf);

  msg.num_iov = 1;
  msg.iov = &iov;
  msg.num_handles = 0;
  msg.handles = NULL;

  /* get message info */
  rc = get_msg(chan, &msg_inf);
  if (rc == ERR_NO_MSG)
    return NO_ERROR; /* no new messages */

  if (rc != NO_ERROR) {
    TLOGE("failed (%d) to get_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  /* read msg content */
  rc = read_msg(chan, msg_inf.id, 0, &msg);
  if (rc < 0) {
    TLOGE("failed (%d) to read_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  /* update number of bytes received */
  iov.iov_len = (size_t) rc;

  /* send message back to the caller */
  rc = send_msg(chan, &msg);
  if (rc < 0) {
    TLOGE("failed (%d) to send_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  /* retire message */
  rc = put_msg(chan, msg_inf.id);
  if (rc != NO_ERROR) {
    TLOGE("failed (%d) to put_msg for chan (%d)\n",
      rc, chan);
    return rc;
  }

  return NO_ERROR;
}

/*
 *  Channel event handler
 */
static void handle_channel_event(const uevent_t * ev) {
  int rc;

  if (ev->event & IPC_HANDLE_POLL_MSG) {
    rc = handle_msg(ev->handle);
    if (rc != NO_ERROR) {
      /* report an error and close channel */
      TLOGE("failed (%d) to handle event on channel %d\n",
        rc, ev->handle);
      close(ev->handle);
    }
    return;
  }
  if (ev->event & IPC_HANDLE_POLL_HUP) {
    /* closed by peer. */
    close(ev->handle);
    return;
  }
}

/*
 *  Port event handler
 */
static void handle_port_event(const uevent_t * ev) {
  uuid_t peer_uuid;

  if ((ev->event & IPC_HANDLE_POLL_ERROR) ||
    (ev->event & IPC_HANDLE_POLL_HUP) ||
    (ev->event & IPC_HANDLE_POLL_MSG) ||
    (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) {
    /* should never happen with port handles */
    TLOGE("error event (0x%x) for port (%d)\n",
      ev->event, ev->handle);
    abort();
  }
  if (ev->event & IPC_HANDLE_POLL_READY) {
    /* incoming connection: accept it */
    int rc = accept(ev->handle, &peer_uuid);
    if (rc < 0) {
      TLOGE("failed (%d) to accept on port %d\n",
        rc, ev->handle);
      return;
    }
    handle_t chan = rc;
    while (true){
      struct uevent cev;

      rc = wait(chan, &cev, INFINITE_TIME);
      if (rc < 0) {
        TLOGE("wait returned (%d)\n", rc);
        abort();
      }
      handle_channel_event(&cev);
      if (cev.event & IPC_HANDLE_POLL_HUP) {
        return;
      }
    }
  }
}


/*
 *  Main application entry point
 */
int main(void) {
  int rc;
  handle_t port;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

abort_test:
  close(chan);
  return rc;
}

Niezabezpieczone światowe interfejsy API i aplikacje

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

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

Przestrzeń użytkownika Trusty IPC Client API

Biblioteka interfejsu API klienta Trusty IPC w przestrzeni użytkownika to cienka warstwa na wierzchu węzła urządzenia fd .

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

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

Powstały deskryptor pliku może być używany wyłącznie do komunikacji z usługą, dla której został utworzony. Deskryptor pliku powinien zostać zamknięty poprzez wywołanie tipc_close() , gdy połączenie nie jest już potrzebne.

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

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

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

tipc_connect()

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

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

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

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

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

tipc_close()

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

int tipc_close(int fd);

[w] fd : Deskryptor pliku poprzednio otwarty przez wywołanie tipc_connect()

API klienta Kernel Trusty IPC

Dla sterowników jądra dostępny jest interfejs API klienta Trusty IPC Client. Na tym API zaimplementowano przestrzeń użytkownika Trusty IPC API.

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

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

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

Po pomyślnym umieszczeniu w kolejce obiekt wywołujący powinien zapomnieć o buforze komunikatów, ponieważ bufor komunikatów ostatecznie wraca do puli wolnych buforów po przetworzeniu przez stronę zdalną (w celu późniejszego ponownego wykorzystania w przypadku innych komunikatów). Użytkownik musi wywołać funkcję tipc_chan_put_txbuf() tylko wtedy, gdy nie uda mu się ustawić takiego bufora w kolejce lub nie jest on już potrzebny.

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

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

Metody w API klienta Kernel Trusty IPC

tipc_create_channel()

Tworzy i konfiguruje instancję kanału Trusty IPC dla konkretnego urządzenia Trusty-ipc.

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

[in] dev : Wskaźnik do zaufanego-ipc, dla którego utworzono kanał urządzenia

[in] ops : Wskaźnik do struct tipc_chan_ops z wypełnionymi wywołaniami zwrotnymi specyficznymi dla wywołującego

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

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

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

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

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

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

  • TIPC_CHANNEL_CONNECTED - wskazuje pomyślne połączenie ze stroną zdalną
  • TIPC_CHANNEL_DISCONNECTED - wskazuje, że strona zdalna odrzuciła żądanie nowego połączenia lub zażądała rozłączenia wcześniej połączonego kanału
  • TIPC_CHANNEL_SHUTDOWN — wskazuje, że strona zdalna zamyka się, trwale kończąc wszystkie połączenia

Wywołanie zwrotne struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) jest wywoływane w celu powiadomienia o odebraniu nowej wiadomości na określonym kanale:

  • [w] cb_arg : Wskaźnik do danych przekazanych do wywołania 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ó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 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 wywołanie tipc_create_chan()

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

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

Osoba wywołująca jest powiadamiana o nawiązaniu połączenia poprzez wywołanie zwrotne handle_event .

tipc_chan_shutdown()

Kończy połączenie z usługą Trusty IPC zainicjowane wcześniej przez wywołanie tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

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

tipc_chan_destroy()

Niszczy określony kanał Trusty IPC.

void tipc_chan_destroy(struct tipc_chan *chan);

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

tipc_chan_get_txbuf_timeout()

Uzyskuje bufor komunikatów, którego można użyć do wysyłania danych za pośrednictwem określonego kanału. Jeśli bufor nie jest natychmiast dostępny, obiekt wywołujący może zostać zablokowany na określony limit czasu (w milisekundach).

struct tipc_msg_buf *
tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);

[in] chan : Wskaźnik do kanału, do którego należy kolejkować wiadomość

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

[retval]: Prawidłowy bufor komunikatów w przypadku powodzenia, ERR_PTR(err) w przypadku błędu

tipc_chan_queue_msg()

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

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

[in] chan : Wskaźnik do kanału, do którego należy kolejkować wiadomość

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

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

tipc_chan_put_txbuf()

Zwalnia określony bufor komunikatów Tx uzyskany wcześniej przez wywołanie tipc_chan_get_txbuf_timeout() .

void tipc_chan_put_txbuf(struct tipc_chan *chan,
                         struct tipc_msg_buf *mb);

[in] chan : Wskaźnik do kanału, do którego należy ten bufor komunikatów

[in] mb : Wskaźnik do bufora komunikatów do zwolnienia

[retval]: Brak

tipc_chan_get_rxbuf()

Uzyskuje nowy bufor komunikatów, którego można używać do odbierania komunikatów za pośrednictwem określonego kanału.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : Wskaźnik do kanału, do którego należy ten bufor komunikatów

[retval]: Prawidłowy bufor komunikatów w przypadku powodzenia, ERR_PTR(err) w przypadku błędu

tipc_chan_put_rxbuf()

Zwalnia określony bufor komunikatów uzyskany wcześniej przez wywołanie tipc_chan_get_rxbuf() .

void tipc_chan_put_rxbuf(struct tipc_chan *chan,
                         struct tipc_msg_buf *mb);

[in] chan : Wskaźnik do kanału, do którego należy ten bufor komunikatów

[in] mb : Wskaźnik do bufora komunikatów do zwolnienia

[retval]: Brak