Trusty API 참조

Trusty는 다음 두 가지 애플리케이션/서비스 클래스를 개발하기 위한 API를 제공합니다.

  • 신뢰할 수 있는 애플리케이션 또는 서비스(TEE 프로세서에서 실행됨)
  • 신뢰할 수 있는 애플리케이션에서 제공하는 서비스를 사용하는 일반 애플리케이션/신뢰할 수 없는 애플리케이션(메인 프로세서에서 실행됨)

Trusty API는 일반적으로 비보안 환경과의 통신을 비롯한 Trusty 프로세스 간 통신(IPC) 시스템을 설명합니다. 메인 프로세서에서 실행되는 소프트웨어는 Trusty API를 사용하여 신뢰할 수 있는 애플리케이션/서비스에 연결되고 IP를 통한 네트워크 서비스처럼 임의의 메시지를 주고받을 수 있습니다. 애플리케이션은 앱 수준 프로토콜을 사용하여 메시지의 데이터 형식과 의미 체계를 결정합니다. 기본 Trusty 인프라(메인 프로세서에서 실행되는 드라이버 형태)를 통해 메시지가 안정적으로 전달되며 통신은 완전한 비동기식으로 동작합니다.

포트 및 채널

Trusty 애플리케이션에서 포트는 클라이언트가 연결되는 이름이 지정된 경로 형태로 서비스 엔드 포인트를 나타내는 데 사용됩니다. 이 경로는 클라이언트가 사용할 단순한 문자열 기반 서비스 ID로 표시됩니다. 역방향 DNS 스타일의 이름 지정(예: com.google.servicename) 규칙이 적용됩니다.

클라이언트가 포트에 연결되면 서비스와 상호작용할 채널을 수신합니다. 서비스는 수신 연결을 허용해야 하며 이 연결을 허용하게 되면 채널도 수신됩니다. 본질적으로 포트는 서비스를 검색하는 데 사용되며 연결된 채널 쌍을 통해 통신이 이루어집니다. 즉, 포트에 연결 인스턴스를 추가합니다. 클라이언트가 포트에 연결되면 대칭형 양방향 연결이 설정됩니다. 이러한 전이중 경로를 통해 클라이언트와 서버는 한쪽에서 연결을 해제할 때까지 임의의 메시지를 주고받을 수 있습니다.

보안 측의 신뢰할 수 있는 애플리케이션 또는 Trusty 커널 모듈만 포트를 만들 수 있습니다. 일반적으로 비보안 측에서 실행되는 애플리케이션은 보안 측에서 게시된 서비스에만 연결할 수 있습니다.

요구사항에 따라, 신뢰할 수 있는 애플리케이션은 클라이언트인 동시에 서버가 될 수 있습니다. 서버로서 서비스를 게시하는 신뢰할 수 있는 애플리케이션은 클라이언트로서 다른 서비스에 연결해야 할 수 있습니다.

Handle API

핸들은 포트 및 채널과 같은 리소스를 나타내는 부호 없는 정수이며 UNIX의 파일 설명자와 유사합니다. 생성된 핸들은 애플리케이션별 핸들 테이블에 배치되며 추후에 참조할 수 있습니다.

호출자는 set_cookie() 메서드를 사용하여 비공개 데이터를 핸들과 연결할 수 있습니다.

Handle API의 메서드

핸들은 애플리케이션의 컨텍스트에서만 유효합니다. 애플리케이션은 명시적으로 지정되지 않는 한 핸들 값을 다른 애플리케이션에 전달해서는 안 됩니다. 핸들 값은 애플리케이션에서 핸들이 잘못되었거나 설정되지 않았음을 나타내는 데 사용하는 INVALID_IPC_HANDLE #define,과 비교해서만 해석되어야 합니다.

호출자가 제공한 비공개 데이터를 지정된 핸들과 연결합니다.

long set_cookie(uint32_t handle, void *cookie)

[in] handle: API 호출 중 하나에서 반환된 핸들

[in] cookie: Trusty 애플리케이션의 임의의 사용자 공간 데이터를 가리키는 포인터

[retval]: 완료 시 NO_ERROR, 실패 시 오류 코드 < 0

호출은 핸들이 생성된 후에 발생하는 이벤트를 처리할 때 유용합니다. 이벤트 처리 메커니즘은 핸들과 쿠키를 이벤트 핸들러에 다시 제공합니다.

wait() 호출을 사용하여 핸들을 이벤트에 사용할 수 있습니다.

wait()

특정 기간 동안 지정된 핸들에서 이벤트가 발생할 때까지 대기합니다.

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

[in] handle_id: API 호출 중 하나에서 반환된 핸들

[out] event: 핸들에서 발생한 이벤트 구조를 가리키는 포인터

[in] timeout_msecs: 밀리초 단위의 시간 제한 값. -1은 제한 시간 초과

[retval]: 유효한 이벤트가 제한 시간 내에 발생한 경우 NO_ERROR, 제한 시간을 초과하였지만 이벤트가 발생하지 않은 경우 ERR_TIMED_OUT, 기타 오류 < 0

완료 시(retval == NO_ERROR) wait() 호출에서 지정된 uevent_t 구조를 발생한 이벤트 관련 정보로 채웁니다.

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;

event 필드에는 다음 값의 조합이 포함됩니다.

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 - 대기 중인 이벤트가 없으므로 호출자가 대기를 다시 시작해야 함

IPC_HANDLE_POLL_ERROR - 지정되지 않은 내부 오류 발생

IPC_HANDLE_POLL_READY - 핸들 유형에 따라 다음과 같습니다.

  • 포트의 경우, 이 값은 대기 중인 연결이 있음을 나타냅니다.
  • 채널의 경우, 이 값은 비동기 연결(connect() 참조)이 되었다는 것을 나타냅니다.

다음 이벤트는 채널과만 관련이 있습니다.

  • IPC_HANDLE_POLL_HUP - 피어에 의해 채널이 폐쇄되었음을 나타냅니다.
  • IPC_HANDLE_POLL_MSG -이 채널에 대기 중인 메시지가 있음을 나타냅니다.
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - 이전에 전송 차단된 호출자가 메시지를 다시 보내려고 시도할 수 있음을 나타냅니다. 자세한 내용은 send_msg() 설명을 참조하세요.

여러 비트가 동시에 설정될 수 있으므로 이벤트 핸들러는 지정된 이벤트의 조합을 처리할 준비가 되어 있어야 합니다. 예를 들어 채널에서는 대기 중인 메시지와 피어에 의해 끊어진 연결이 동시에 있을 수 있습니다.

이벤트 대부분은 고정되어 있습니다. 이벤트는 기본 조건이 지속되는 동안 유지됩니다. 예를 들어 대기 중인 메시지는 모두 수신되고 대기 중인 연결 요청은 처리됩니다. 예외는 다음과 같습니다. IPC_HANDLE_POLL_SEND_UNBLOCKED 이벤트의 경우 읽음과 동시에 승인되며 애플리케이션은 단 1회에 걸쳐 이벤트를 처리합니다.

핸들은 close() 메서드를 호출하여 제거할 수 있습니다.

close()

지정된 핸들과 연결된 리소스를 제거하고 핸들 테이블에서 이를 삭제합니다.

long close(uint32_t handle_id);

[in] handle_id: 제거 처리

[retval]: 완료 시 0, 실패 시 음수 오류

Server API

서비스 엔드 포인트를 나타내는 이름이 지정된 포트를 하나 이상 생성하여 서버를 시작합니다. 각 포트는 핸들로 표시됩니다.

Server API의 메서드

port_create()

이름이 지정된 서비스 포트를 생성합니다.

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

[in] path: 위에서 설명한 대로 포트의 문자열 이름입니다. 시스템 전체에서 고유한 이름을 사용해야 합니다. 중복된 이름은 생성할 수 없습니다.

[in] num_recv_bufs: 클라이언트와의 데이터 교환이 용이하도록 포트의 채널이 사전에 할당할 수 있는 최대 버퍼 수입니다. 버퍼는 양방향으로 전송되는 데이터에 있어 개별적으로 계산되므로, 1을 지정하면 전송 버퍼 1개와 수신 버퍼 1개가 사전 할당됩니다. 일반적으로 필요한 버퍼 수는 클라이언트와 서버 간의 상위 프로토콜 계약에 따라 다릅니다. 메시지를 발신하고 다른 메시지를 발신하기 전에 답장을 수신하는 동기 프로토콜의 경우 버퍼 수는 최소 1개입니다. 답장이 표시되기 전에 클라이언트가 2개 이상의 메시지(예: 프롤로그 메시지 1개 및 실제 명령어 메시지 1개)를 전송할 필요가 있다면 더 많은 버퍼가 할당될 수 있습니다. 채널마다 버퍼 세트가 할당되어 있으므로 별도의 두 연결(채널)은 별도의 버퍼 세트를 갖게 됩니다.

[in] recv_buf_size: 위의 버퍼 세트에서 개별 버퍼의 최대 크기입니다. 이 값은 프로토콜에 따라 달라지며, 피어와 주고받을 수 있는 메시지의 최대 크기를 효과적으로 제한합니다.

[in] flags: 추가 포트 동작을 지정하는 플래그 조합입니다.

이 값은 다음 값의 조합입니다.

IPC_PORT_ALLOW_TA_CONNECT - 다른 보안 앱과의 연결을 허용합니다.

IPC_PORT_ALLOW_NS_CONNECT - 비보안 환경과의 연결을 허용합니다.

[retval]: 음수가 아닌 경우 생성된 포트 핸들 또는 음수인 경우 특정 오류 핸들

그다음에 서버는 wait() 호출을 사용하여 포트 핸들 목록에서 수신 연결을 폴링합니다. uevent_t 구조의 event 필드 내에 설정된 IPC_HANDLE_POLL_READY 비트에 나타난 연결 요청을 수신하면 서버는 accept()를 호출하여 연결을 완료하고 수신 메시지를 폴링할 수 있는 채널(다른 핸들로 표현됨)을 만들어야 합니다.

accept()

수신 연결을 허용하고 채널의 핸들을 가져옵니다.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id: 클라이언트가 연결된 포트를 나타내는 핸들

[out] peer_uuid: uuud_t 구조를 가리키는 포인터를 연결 클라이언트 애플리케이션의 UUID로 채웁니다. 연결이 비보안 환경에서 시작된 경우 모두 0으로 설정됩니다.

[retval]: 음수가 아닌 경우 서버가 클라이언트와 메시지를 주고받을 수 있는 채널의 핸들. 그렇지 않은 경우 오류 코드 반환

Client API

이 섹션은 Client API의 메서드를 포함하고 있습니다.

Client API의 메서드

connect()

이름이 지정된 포트와 연결을 시작합니다.

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

[in] path: Trusty 애플리케이션에서 게시한 포트의 이름

[in] flags: 추가, 선택 동작 지정

[retval]: 서버와 메시지를 주고받을 수 있는 채널의 핸들. 음수인 경우 오류

지정된 flags가 없는 경우(flags 매개변수가 0으로 설정된 경우) connect()를 호출하면 지정된 포트에 동기식 연결이 시작되고(이 포트가 없는 경우 즉시 오류가 반환됨) 서버가 연결을 허용할 때까지 블록이 생성됩니다.

이 동작은 아래에 설명된 두 값의 조합을 지정하여 변경할 수 있습니다.

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

IPC_CONNECT_WAIT_FOR_PORT - 실행 시 지정된 포트가 즉시 존재하지 않는다면 실행이 즉시 중단되는 것이 아니라 connect() 호출이 대기하도록 합니다.

IPC_CONNECT_ASYNC - 설정 시 비동기 연결을 시작합니다. 애플리케이션은 일반 작업을 시작하기 전에 uevent_t 구조의 이벤트 필드에 설정된 IPC_HANDLE_POLL_READY 비트가 나타내는 연결 완료 이벤트에 관해 wait()를 호출하여 반환된 핸들을 폴링해야 합니다.

Messaging API

Messaging API 호출은 이전에 설정한 연결(채널)을 통해 메시지를 전송하고 읽을 수 있도록 합니다. Messaging API 호출은 서버와 클라이언트에서 동일합니다.

클라이언트는 connect() 호출을 통해 채널의 핸들을 수신하고 서버는 위에서 설명한 것처럼 accept() 호출을 통해 채널의 핸들을 수신합니다.

Trusty 메시지 구조

Trusty API로 주고받는 메시지는 다음과 같이 최소한의 구조로 되어 있어 서버와 클라이언트가 실제 콘텐츠의 의미에 동의하도록 합니다.

/*
 *  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;

메시지는 iovec_t 구조의 배열로 표시되는 하나 이상의 비연속 버퍼로 구성될 수 있습니다. Trusty는 분산-수집 읽기 작업을 실행하며 iov 배열을 통해 블록에 쓰기 작업을 실행합니다. iov 배열이 설명하는 버퍼의 내용은 완전히 임의적입니다.

Messaging API의 메서드

send_msg()

지정된 채널을 통해 메시지를 전송합니다.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle: 메시지를 보낼 채널 핸들

[in] msg: 메시지를 설명하는 ipc_msg_t structure를 가리키는 포인터

[retval]: 완료 시 전송된 총 바이트 수, 실패 시 음수 오류

클라이언트 또는 서버가 채널을 통해 메시지를 보내려고 할 때, 대상 피어 메시지 대기열에 공간이 없으면 채널이 발신 차단 상태가 되어 ERR_NOT_ENOUGH_BUFFER 오류 코드를 반환할 수 있습니다. 이 작업은 단순한 동기식 요청/응답 프로토콜에서 발생하지 않으며, 더 복잡한 경우에 발생할 수 있습니다. 이러한 경우 호출자는 피어가 수신 대기열에 여유 공간을 마련할 때까지 기다려야 합니다. wait() 호출이 반환하는 uevent_t 구조의 event 필드에 설정된 IPC_HANDLE_POLL_SEND_UNBLOCKED 비트로 표시되는 메시지 처리 및 폐기로 여유 공간이 만들어집니다.

get_msg()

지정된 채널의 수신 메시지 대기열에서 다음 메시지와 관련된

메타 정보를 가져옵니다.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle: 새 메시지를 가져오는 채널의 핸들

[out] msg_info : 다음과 같이 설명되는 메시지 정보 구조입니다.

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

각 메시지에는 전송되지 않은 메시지 집합의 고유 ID가 할당되며 각 메시지의 전체 길이가 채워집니다. 프로토콜에 의해 구성되고 허용되는 경우 특정 채널에 있어 미전송된 메시지가 한 번에 여러 개 있을 수 있습니다.

[retval]: 완료 시 NO_ERROR, 실패 시 음수 오류

read_msg()

지정된 오프셋에서 시작하여 지정된 ID로 메시지의 내용을 읽습니다.

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

[in] handle: 메시지를 읽을 채널의 핸들

[in] msg_id: 읽을 메시지의 ID

[in] offset: 읽으려는 메시지의 오프셋

[in] msg: 수신 메시지 데이터를 저장하는 버퍼 세트를 설명하는 ipc_msg_t 구조를 가리키는 포인터

[retval]: 완료 시 dst 버퍼에 저장되는 총 바이트 수, 실패 시 음수 오류

필요에 따라 다른 오프셋에서 시작하여 여러 번 read_msg 메서드를 호출할 수 있으며, 오프셋은 순차적일 필요가 없습니다.

put_msg()

지정된 ID로 메시지를 폐기합니다.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle: 메시지가 수신된 채널의 핸들

[in] msg_id: 폐기되는 메시지의 ID

[retval]: 완료 시 NO_ERROR, 실패 시 음수 오류

메시지가 폐기되고 메시지가 차지한 버퍼가 해제된 후에는 메시지 콘텐츠에 액세스할 수 없습니다.

File Descriptor API

File Descriptor API는 read(), write(), ioctl() 호출을 포함하고 있습니다. 이러한 호출은 기존에 작은 숫자로 표시되는 파일 설명자의 사전 정의된 정적 세트에서 작동할 수 있습니다. 현재 구현에서 파일 설명자 공간은 IPC 핸들 공간과 별도로 존재합니다. Trusty의 File Descriptor API는 파일 설명자를 기반으로 하는 기존 API와 유사합니다.

기본적으로 다음과 같이 사전 정의된 표준 및 널리 알려진 파일 설명자가 3개 있습니다.

  • 0 - 표준 입력. 신뢰할 수 있는 애플리케이션에는 대화형 콘솔이 없으므로 표준 입력 fd의 기본 구현은 노옵스(no-ops)입니다. 따라서 fd 0에서 ioctl() 읽기, 쓰기, 호출은 ERR_NOT_SUPPORTED 오류를 반환합니다.
  • 1 - 표준 출력. 표준 출력에 작성된 데이터는 LK 디버그 수준에 따라 UART로 라우팅될 수도 있고 플랫폼 및 구성에 따라 비보안 측 메모리 로그로 라우팅될 수도 있습니다. 중요하지 않은 디버그 로그와 메시지는 표준 출력으로 전송해야 합니다. read()ioctl() 메서드는 노옵스(no-ops)이며 ERR_NOT_SUPPORTED 오류를 반환해야 합니다.
  • 2 - 일반 오류. 표준 오류에 기록된 데이터는 플랫폼 및 구성에 따라 UART 또는 비보안 측에서 사용 가능한 메모리 로그로 라우팅되어야 합니다. 스트림은 제한되지 않으므로 중요한 메시지만 표준 오류에 기록하는 것이 좋습니다. read()ioctl() 메서드는 노옵스(no-ops)이며 ERR_NOT_SUPPORTED 오류를 반환해야 합니다.

플랫폼에 특화된 확장을 구현하기 위해 파일 설명자 세트를 확장하여 더 많은 fds를 구현할 수 있지만 파일 설명자 확장은 신중을 요구하는 작업입니다. 파일 설명자를 확장하면 충돌이 쉽게 발생하므로 일반적으로 권장하지 않습니다.

File Descriptor API의 메서드

read()

지정된 파일 설명자로부터 최대 count바이트까지 데이터 읽기 작업을 시도합니다.

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

[in] fd: 읽을 파일 설명자

[out] buf: 데이터를 저장할 버퍼를 가리키는 포인터

[in] count: 읽을 최대 바이트 수

[retval]: 읽은 바이트 수 반환, 그렇지 않은 경우 음수 오류

write()

지정된 파일 설명자에 최대 count바이트까지 데이터 쓰기 작업을 실행합니다.

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

[in] fd: 쓸 파일 설명자

[out] buf: 쓸 데이터를 가리키는 포인터

[in] count: 쓸 최대 바이트 수

[retval]: 쓴 바이트 수 반환, 그렇지 않은 경우 음수 오류

ioctl()

지정된 파일 설명자에 관한 지정된 ioctl 명령어를 호출합니다.

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

[in] fd: ioctl()을 호출할 파일 설명자

[in] cmd: ioctl 명령어

[in/out] args: ioctl() 인수를 가리키는 포인터

Miscellaneous API

Miscellaneous API의 메서드

gettime()

현재 시스템 시간을 나노초 단위로 반환합니다.

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

[in] clock_id: 플랫폼에 따라 다르며, 기본값으로 0 전달

[in] flags: 유보 중으로, 0이어야 함

[in] time: 현재 시간을 저장할 int64_t 값을 가리키는 포인터

[retval]: 완료 시 NO_ERROR, 실패 시 음수 오류

nanosleep()

지정된 기간에 호출 애플리케이션의 실행을 정지하고 이 기간이 지나면 다시 시작합니다.

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

[in] clock_id: 유보 중으로, 0이어야 함

[in] flags: 유보 중으로, 0이어야 함

[in] sleep_time: 나노초 단위로 표시되는 절전 시간

[retval]: 완료 시 NO_ERROR, 실패 시 음수 오류

신뢰할 수 있는 애플리케이션 서버의 예

다음 샘플 애플리케이션은 위 API의 사용법을 보여줍니다. 샘플은 여러 수신 연결을 처리하여 보안 또는 비보안 측면에서 시작된 클라이언트로부터 수신하는 모든 메시지를 호출자에 다시 나타내는 'echo' 서비스를 생성합니다.

#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;
}

run_end_to_end_msg_test() 메서드는 비동기식으로 메시지 10,000개를 'echo' 서비스에 보내고 응답을 처리합니다.

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;
}

비보안 환경 API 및 애플리케이션

보안 측에서 게시되며 IPC_PORT_ALLOW_NS_CONNECT 속성을 가진 Trusty 서비스 세트를 통해 비보안 측에서 실행되는 커널 및 사용자 공간 프로그램에 액세스할 수 있습니다.

커널 및 사용자 공간을 비롯한 비보안 측의 실행 환경은 보안 측의 실행 환경과 크게 다릅니다. 따라서 두 환경 모두에 대해 단일 라이브러리가 아닌 서로 다른 API 세트가 두 개 있습니다. 커널에서 Client API는 trusty-ipc 커널 드라이버에 의해 제공되고 문자 기기 노드를 등록합니다. 이 노드는 사용자 공간 프로세스에서 보안 측에서 실행되는 서비스와 통신하는 데 사용될 수 있습니다.

User space Trusty IPC Client API

User space Trusty IPC Client API 라이브러리는 기기 노드 fd 상단에 위치한 얇은 레이어입니다.

사용자 공간 프로그램은 tipc_connect()를 호출하여 통신 세션을 시작하고, 지정된 Trusty 서비스와 연결을 초기화합니다. 내부적으로 tipc_connect() 호출은 지정된 기기 노드를 열어 파일 설명자를 가져오고, 연결할 서비스 이름이 포함된 문자열을 가리키는 argp 매개변수를 통해 TIPC_IOC_CONNECT ioctl()을 호출합니다.

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

생성된 파일 설명자는 파일이 생성된 서비스와 통신하는 데만 사용할 수 있습니다. 연결이 더 이상 필요하지 않을 때 tipc_close() 호출을 통해 파일 설명자를 종료해야 합니다.

tipc_connect() 호출로 가져온 파일 설명자는 일반적인 문자 기기 노드로 아래와 같이 작동합니다.

  • 필요한 경우 비차단 모드로 전환
  • 표준 write() 호출을 사용하는 쓰기 작업으로 상대방에게 메시지 전송
  • 일반 파일 설명자로서 수신 메시지의 가용성 폴링(poll() 호출 또는 select() 호출 사용)
  • 읽기 작업을 통해 수신 메시지 가져오기

호출자는 지정된 fd에 대해 쓰기 호출을 실행하여 Trusty 서비스에 메시지를 전송합니다. write() 호출로 전달된 모든 데이터는 trusty-ipc 드라이버에 의해 메시지로 변환됩니다. 메시지는 Trusty 커널의 IPC 하위 시스템에서 데이터를 처리하여 적절한 대상으로 라우팅되는 보안 측으로 전달되며, 특정 채널 핸들에서 IPC_HANDLE_POLL_MSG 이벤트로 앱 이벤트 루프에 전달됩니다. 서비스별 프로토콜에 따라, Trusty 서비스는 응답 메시지를 하나 이상 전송하고, 응답 메시지는 비보안 측으로 다시 전송되어 적절한 채널 파일 설명자 메시지 대기열에 배치됩니다. 배치된 메시지는 사용자 공간 애플리케이션 read() 호출로 가져올 수 있습니다.

tipc_connect()

지정한 tipc 기기 노드를 열어 지정된 Trusty 서비스에 연결합니다.

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

[in] dev_name: 열 Trusty IPC 기기 노드의 경로

[in] srv_name: 연결을 시작할 게시된 Trusty 서비스의 이름

[retval]: 완료 시 유효한 파일 설명자, 실패 시 -1.

tipc_close()

파일 설명자에 의해 지정된 Trusty 서비스와의 연결을 끊습니다.

int tipc_close(int fd);

[in] fd: 이전에 tipc_connect() 호출로 열었던 파일 설명자

Kernel Trusty IPC Client API

Kernel Trusty IPC Client API는 커널 드라이버에서 사용할 수 있습니다. User space Trusty IPC API는 Kernel Trusty IPC Client API를 기반으로 구현됩니다.

일반적으로 Kernel Trusty IPC Client API는 호출자가 tipc_create_channel() 함수를 사용하여 struct tipc_chan 객체를 생성하고 tipc_chan_connect() 호출을 사용하여 보안 측에서 실행 중인 Trusty IPC 서비스에 연결하는 방식으로 사용됩니다. 원격 측 연결은 tipc_chan_shutdown()tipc_chan_destroy() 호출을 통해 리소스를 정리하며 종료됩니다.

호출자는 handle_event() 콜백을 통해 연결되었다는 알림을 받으면 다음 작업을 실행합니다.

  • tipc_chan_get_txbuf_timeout() 호출을 사용하여 메시지 버퍼를 가져옵니다.
  • 메시지를 작성하고
  • tipc_chan_queue_msg() 메서드를 사용하여 채널이 연결된 보안 측 Trusty 서비스로 전달하는 메시지를 대기열에 전달합니다.

대기열에 전달한 후, 호출자는 메시지 버퍼를 저장하지 않습니다. 왜냐하면 원격 측에서 처리한 메시지 버퍼는 여유 버퍼 풀로 다시 반환되어 다른 메시지를 보낼 때 다시 사용되기 때문입니다. 사용자는 이러한 버퍼를 대기열에 넣지 못하거나 더 이상 필요하지 않을 때만 tipc_chan_put_txbuf()를 호출합니다.

API 사용자는 trusty-ipc rx 작업 대기열의 컨텍스트에서 호출되는 handle_msg() 알림 콜백을 처리함으로써 원격 측에서 메시지를 수신합니다. 알림 콜백은 처리할 수신 메시지가 포함된 rx 버퍼를 가리키는 포인터를 제공합니다.

handle_msg() 콜백 구현은 유효한 struct tipc_msg_buf를 가리키는 포인터를 반환할 것으로 예상됩니다. 로컬에서 처리되어 더 이상 필요하지 않은 경우, 수신 메시지 버퍼와 동일할 수 있습니다. 또는 수신 버퍼가 추가 처리를 위해 대기 중인 경우, tipc_chan_get_rxbuf() 호출을 통해 새로운 버퍼가 될 수도 있습니다. 분리된 rx 버퍼는 추적되어야 하며, 더 이상 필요하지 않을 때 tipc_chan_put_rxbuf() 호출로 해제해야 합니다.

Kernel Trusty IPC Client API의 메서드

tipc_create_channel()

특정 trusty-ipc 기기에 Trusty IPC 채널의 인스턴스를 만들고 구성합니다.

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

[in] dev: 기기 채널이 생성된 trusty-ipc를 가리키는 포인터

[in] ops: struct tipc_chan_ops를 가리키는 포인터이며, 호출자 관련 콜백이 채워짐

[in] cb_arg: tipc_chan_ops 콜백에 전달할 데이터를 가리키는 포인터

[retval]: 완료 시 새로 만든 인스턴스를 가리키는 포인터 struct tipc_chan, 실패 시 ERR_PTR(err)

일반적으로 호출자는 상응하는 활동이 발생할 때 비동기적으로 호출되는 콜백을 두 차례 제공해야 합니다.

void (*handle_event)(void *cb_arg, int event) 이벤트가 호출되어 호출자에게 채널 상태 변경을 알립니다.

[in] cb_arg: tipc_create_channel() 호출로 전달된 데이터를 가리키는 포인터

[in] event : 다음 값 중 하나일 수 있는 이벤트입니다.

  • TIPC_CHANNEL_CONNECTED - 원격 측에 연결되었음을 나타냅니다.
  • TIPC_CHANNEL_DISCONNECTED - 원격 측에서 새 연결 요청을 거부했거나 이전에 연결된 채널의 연결 해제를 요청했음을 나타냅니다.
  • TIPC_CHANNEL_SHUTDOWN - 원격 측이 종료되고 모든 연결이 영구적으로 종료됨을 나타냅니다.

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) 콜백을 호출하여 지정된 채널을 통해 새로운 메시지가 수신되었다는 알림을 제공합니다.

  • [in] cb_arg: tipc_create_channel() 호출로 전달된 데이터를 가리키는 포인터
  • [in] mb: 수신 메시지를 설명하는 struct tipc_msg_buf를 가리키는 포인터
  • [retval]: 메시지가 로컬에서 처리되어 더 이상 필요하지 않은 경우 또는 tipc_chan_get_rxbuf() 호출이 새로운 버퍼를 가져온 경우, 콜백 구현은 mb 매개변수로서 수신한 동일한 포인터일 수 있는 struct tipc_msg_buf를 가리키는 포인터를 반환합니다.

tipc_chan_connect()

지정된 Trusty IPC 서비스에 연결합니다.

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

[in] chan: tipc_create_chan() 호출에서 반환된 채널을 가리키는 포인터

[in] port: 연결할 서비스 이름이 포함된 문자열을 가리키는 포인터

[retval]: 성공 시 0, 실패 시 음수 오류

연결이 설정되면 호출자에게 handle_event 콜백으로 알립니다.

tipc_chan_shutdown()

이전에 tipc_chan_connect() 호출로 시작된 Trusty IPC 서비스의 연결을 종료합니다.

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan: tipc_create_chan() 호출에서 반환된 채널을 가리키는 포인터

tipc_chan_destroy()

지정된 Trusty IPC 채널을 삭제합니다.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan: tipc_create_chan() 호출에서 반환된 채널을 가리키는 포인터

tipc_chan_get_txbuf_timeout()

지정된 채널을 통해 데이터를 전송하는 데 사용할 수 있는 메시지 버퍼를 가져옵니다. 버퍼를 즉시 사용할 수 없는 경우 호출자는 밀리초 단위의 지정된 시간 동안 차단될 수 있습니다.

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

[in] chan: 메시지를 대기열에 추가할 채널을 가리키는 포인터

[in] chan: tx 버퍼를 사용할 수 있을 때까지 대기하는 최대 시간 제한

[retval]: 성공 시 유효한 메시지 버퍼, 오류 발생 시 ERR_PTR(err)

tipc_chan_queue_msg()

지정된 Trusty IPC 채널을 통해 전송할 메시지를 대기열에 넣습니다.

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

[in] chan: 메시지를 대기열에 추가할 채널을 가리키는 포인터

[in] mb: 대기열에 있는 메시지를 가리키는 포인터이며 tipc_chan_get_txbuf_timeout() 호출로 가져옴

[retval]: 성공 시 0, 실패 시 음수 오류

tipc_chan_put_txbuf()

이전에 tipc_chan_get_txbuf_timeout() 호출이 가져온 지정된 Tx 메시지 버퍼를 해제합니다.

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

[in] chan: 이 메시지 버퍼가 속한 채널을 가리키는 포인터

[in] mb: 해제할 메시지 버퍼를 가리키는 포인터

[retval]: 없음

tipc_chan_get_rxbuf()

지정된 채널을 통해 메시지를 수신하는 데 사용할 수 있는 새 메시지 버퍼를 가져옵니다.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan: 이 메시지 버퍼가 속한 채널을 가리키는 포인터

[retval]: 성공 시 유효한 메시지 버퍼, 오류 발생 시 ERR_PTR(err)

tipc_chan_put_rxbuf()

이전에 tipc_chan_get_rxbuf() 호출이 가져온 지정된 메시지 버퍼를 해제합니다.

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

[in] chan: 이 메시지 버퍼가 속한 채널을 가리키는 포인터

[in] mb: 해제할 메시지 버퍼를 가리키는 포인터

[retval]: 없음