Tài liệu tham khảo về API Trusty

Trusty cung cấp các API để phát triển hai loại ứng dụng và dịch vụ:

  • Các ứng dụng và dịch vụ đáng tin cậy chạy trên bộ xử lý TEE
  • Ứng dụng thông thường và không đáng tin cậy chạy trên bộ xử lý chính và sử dụng các dịch vụ do ứng dụng đáng tin cậy cung cấp

API Trusty thường mô tả hệ thống giao tiếp giữa các quá trình (IPC) Trusty, bao gồm cả giao tiếp với thế giới không an toàn. Phần mềm chạy trên bộ xử lý chính có thể sử dụng API đáng tin cậy để kết nối với các ứng dụng và dịch vụ đáng tin cậy, đồng thời trao đổi thông báo tuỳ ý với các ứng dụng và dịch vụ đó giống như một dịch vụ mạng qua IP. Ứng dụng có thể xác định định dạng dữ liệu và ngữ nghĩa của các thông báo này bằng cách sử dụng giao thức cấp ứng dụng. Việc phân phối tin nhắn đáng tin cậy được đảm bảo bằng cơ sở hạ tầng Trusty cơ bản (ở dạng trình điều khiển chạy trên bộ xử lý chính) và hoạt động giao tiếp hoàn toàn không đồng bộ.

Cổng và kênh

Các cổng được ứng dụng Trusty sử dụng để hiển thị các điểm cuối dịch vụ ở dạng đường dẫn được đặt tên mà ứng dụng khách kết nối. Thao tác này sẽ cung cấp một mã dịch vụ đơn giản, dựa trên chuỗi để ứng dụng khách sử dụng. Quy ước đặt tên là đặt tên theo kiểu DNS ngược, ví dụ: com.google.servicename.

Khi kết nối với một cổng, ứng dụng sẽ nhận được một kênh để tương tác với một dịch vụ. Dịch vụ phải chấp nhận kết nối đến và khi chấp nhận, dịch vụ cũng sẽ nhận được một kênh. Về cơ bản, cổng được dùng để tra cứu dịch vụ, sau đó quá trình giao tiếp sẽ diễn ra qua một cặp kênh đã kết nối (tức là các thực thể kết nối trên một cổng). Khi một ứng dụng kết nối với một cổng, một kết nối hai chiều, đối xứng sẽ được thiết lập. Khi sử dụng đường dẫn toàn bộ song công này, ứng dụng và máy chủ có thể trao đổi thông báo tuỳ ý cho đến khi một bên quyết định huỷ kết nối.

Chỉ những ứng dụng đáng tin cậy ở phía bảo mật hoặc mô-đun hạt nhân Trusty mới có thể tạo các cổng. Các ứng dụng chạy ở phía không an toàn (trong thế giới thực) chỉ có thể kết nối với các dịch vụ do phía an toàn phát hành.

Tuỳ thuộc vào yêu cầu, ứng dụng đáng tin cậy có thể vừa là ứng dụng khách vừa là máy chủ. Một ứng dụng đáng tin cậy phát hành một dịch vụ (dưới dạng máy chủ) có thể cần kết nối với các dịch vụ khác (dưới dạng ứng dụng khách).

Xử lý API

Tay điều khiển là các số nguyên chưa ký đại diện cho các tài nguyên như cổng và kênh, tương tự như chỉ số mô tả tệp trong UNIX. Sau khi được tạo, các handle sẽ được đặt vào bảng handle dành riêng cho ứng dụng và có thể được tham chiếu sau.

Phương thức gọi có thể liên kết dữ liệu riêng tư với một tên người dùng bằng cách sử dụng phương thức set_cookie().

Các phương thức trong API Handle

Tên người dùng chỉ hợp lệ trong ngữ cảnh của một ứng dụng. Ứng dụng không được truyền giá trị của tên người dùng sang các ứng dụng khác trừ phi được chỉ định rõ ràng. Bạn chỉ nên diễn giải giá trị tay cầm bằng cách so sánh giá trị đó với INVALID_IPC_HANDLE #define, mà ứng dụng có thể sử dụng làm chỉ báo cho biết tay cầm không hợp lệ hoặc chưa được đặt.

Liên kết dữ liệu riêng tư do phương thức gọi cung cấp với một tên người dùng đã chỉ định.

long set_cookie(uint32_t handle, void *cookie)

[trong] handle: Mọi tay cầm do một trong các lệnh gọi API trả về

[trong] cookie: Con trỏ đến dữ liệu không gian người dùng tuỳ ý trong ứng dụng Trusty

[retval]: NO_ERROR nếu thành công, mã lỗi < 0 nếu không

Lệnh gọi này hữu ích trong việc xử lý các sự kiện khi chúng xảy ra sau khi lệnh gọi xử lý được tạo. Cơ chế xử lý sự kiện cung cấp handle và cookie của handle đó trở lại trình xử lý sự kiện.

Bạn có thể chờ các sự kiện bằng cách sử dụng lệnh gọi wait().

wait()

Chờ một sự kiện xảy ra trên một tay điều khiển nhất định trong một khoảng thời gian cụ thể.

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

[trong] handle_id: Mọi tay cầm do một trong các lệnh gọi API trả về

[out] event: Con trỏ đến cấu trúc đại diện cho một sự kiện đã xảy ra trên tay cầm này

[trong] timeout_msecs: Giá trị thời gian chờ tính bằng mili giây; giá trị -1 là thời gian chờ vô hạn

[retval]: NO_ERROR nếu một sự kiện hợp lệ xảy ra trong khoảng thời gian chờ đã chỉ định; ERR_TIMED_OUT nếu khoảng thời gian chờ đã chỉ định đã trôi qua nhưng không có sự kiện nào xảy ra; < 0 đối với các lỗi khác

Khi thành công (retval == NO_ERROR), lệnh gọi wait() sẽ điền thông tin về sự kiện đã xảy ra vào một cấu trúc uevent_t đã chỉ định.

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;

Trường event chứa tổ hợp các giá trị sau:

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 – không có sự kiện nào thực sự đang chờ xử lý, người gọi nên bắt đầu lại quá trình chờ

IPC_HANDLE_POLL_ERROR – đã xảy ra lỗi nội bộ không xác định

IPC_HANDLE_POLL_READY – tuỳ thuộc vào loại tên người dùng, như sau:

  • Đối với cổng, giá trị này cho biết có một kết nối đang chờ xử lý
  • Đối với các kênh, giá trị này cho biết rằng một kết nối không đồng bộ (xem connect()) đã được thiết lập

Các sự kiện sau đây chỉ liên quan đến kênh:

  • IPC_HANDLE_POLL_HUP – cho biết một kênh đã bị một đối tác đóng
  • IPC_HANDLE_POLL_MSG – cho biết có một tin nhắn đang chờ xử lý cho kênh này
  • IPC_HANDLE_POLL_SEND_UNBLOCKED – cho biết rằng một phương thức gọi bị chặn gửi trước đó có thể cố gắng gửi lại một thông báo (xem nội dung mô tả về send_msg() để biết thông tin chi tiết)

Bạn nên chuẩn bị trình xử lý sự kiện để xử lý tổ hợp các sự kiện được chỉ định, vì nhiều bit có thể được đặt cùng một lúc. Ví dụ: đối với một kênh, có thể có thông báo đang chờ xử lý và một kết nối do một máy ngang hàng đóng cùng lúc.

Hầu hết các sự kiện đều cố định. Các trạng thái này vẫn tồn tại miễn là điều kiện cơ bản vẫn tồn tại (ví dụ: tất cả thông báo đang chờ xử lý đều được nhận và các yêu cầu kết nối đang chờ xử lý đều được xử lý). Trường hợp ngoại lệ là trường hợp của sự kiện IPC_HANDLE_POLL_SEND_UNBLOCKED. Sự kiện này sẽ bị xoá sau khi đọc và ứng dụng chỉ có một cơ hội để xử lý sự kiện đó.

Bạn có thể huỷ các tay điều khiển bằng cách gọi phương thức close().

close()

Huỷ bỏ tài nguyên được liên kết với handle đã chỉ định và xoá tài nguyên đó khỏi bảng handle.

long close(uint32_t handle_id);

[trong] handle_id: Xử lý để huỷ

[retval]: 0 nếu thành công; lỗi âm nếu không

API máy chủ

Máy chủ bắt đầu bằng cách tạo một hoặc nhiều cổng được đặt tên đại diện cho các điểm cuối dịch vụ của máy chủ. Mỗi cổng được biểu thị bằng một tay cầm.

Các phương thức trong API máy chủ

port_create()

Tạo một cổng dịch vụ được đặt tên.

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

[trong] path: Tên chuỗi của cổng (như mô tả ở trên). Tên này phải là duy nhất trên hệ thống; các nỗ lực tạo tên trùng lặp sẽ không thành công.

[trong] num_recv_bufs: Số lượng bộ đệm tối đa mà một kênh trên cổng này có thể phân bổ trước để tạo điều kiện trao đổi dữ liệu với ứng dụng. Các vùng đệm được tính riêng cho dữ liệu đi theo cả hai hướng, vì vậy, việc chỉ định 1 ở đây có nghĩa là 1 vùng đệm gửi và 1 vùng đệm nhận được phân bổ trước. Nhìn chung, số lượng vùng đệm cần thiết phụ thuộc vào thoả thuận giao thức cấp cao hơn giữa ứng dụng và máy chủ. Số lượng có thể chỉ là 1 trong trường hợp giao thức rất đồng bộ (gửi thông báo, nhận thư trả lời trước khi gửi thông báo khác). Nhưng số lượng có thể nhiều hơn nếu ứng dụng dự kiến gửi nhiều thông báo trước khi có thể trả lời (ví dụ: một thông báo là lời mở đầu và một thông báo khác là lệnh thực tế). Các nhóm bộ đệm được phân bổ là theo kênh, vì vậy, hai kết nối (kênh) riêng biệt sẽ có các nhóm bộ đệm riêng biệt.

[trong] recv_buf_size: Kích thước tối đa của từng bộ đệm riêng lẻ trong bộ đệm ở trên. Giá trị này phụ thuộc vào giao thức và giới hạn hiệu quả kích thước tối đa của thông báo mà bạn có thể trao đổi với máy ngang hàng

[trong] flags: Một tổ hợp cờ chỉ định hành vi cổng bổ sung

Giá trị này phải là tổ hợp của các giá trị sau:

IPC_PORT_ALLOW_TA_CONNECT – cho phép kết nối từ các ứng dụng bảo mật khác

IPC_PORT_ALLOW_NS_CONNECT – cho phép kết nối từ môi trường không an toàn

[retval]: Giá trị xử lý của cổng được tạo nếu không âm hoặc một lỗi cụ thể nếu âm

Sau đó, máy chủ sẽ thăm dò ý kiến danh sách tay cầm cổng cho các kết nối đến bằng lệnh gọi wait(). Khi nhận được yêu cầu kết nối được biểu thị bằng bit IPC_HANDLE_POLL_READY được đặt trong trường event của cấu trúc uevent_t, máy chủ sẽ gọi accept() để hoàn tất việc thiết lập kết nối và tạo một kênh (được biểu thị bằng một tay điều khiển khác) sau đó có thể được thăm dò ý kiến về các thông báo đến.

accept()

Chấp nhận kết nối đến và nhận tên người dùng cho một kênh.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[trong] handle_id: Tên xử lý đại diện cho cổng mà ứng dụng đã kết nối

[out] peer_uuid: Con trỏ đến cấu trúc uuid_t sẽ được điền bằng UUID của ứng dụng khách đang kết nối. Giá trị này được đặt thành tất cả các số 0 nếu kết nối bắt nguồn từ thế giới không an toàn

[retval]: Tên xử lý cho một kênh (nếu không âm) mà máy chủ có thể trao đổi thông báo với ứng dụng (hoặc mã lỗi nếu không)

API ứng dụng

Phần này chứa các phương thức trong API ứng dụng.

Các phương thức trong API ứng dụng

connect()

Khởi tạo kết nối với một cổng được chỉ định theo tên.

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

[trong] path: Tên của một cổng do ứng dụng Trusty phát hành

[trong] flags: Chỉ định hành vi bổ sung, không bắt buộc

[retval]: Tên xử lý của một kênh mà qua đó có thể trao đổi thông báo với máy chủ; lỗi nếu giá trị âm

Nếu không chỉ định flags (tham số flags được đặt thành 0), thì việc gọi connect() sẽ bắt đầu một kết nối đồng bộ với một cổng được chỉ định. Kết nối này sẽ trả về lỗi ngay lập tức nếu cổng không tồn tại và tạo một khối cho đến khi máy chủ chấp nhận kết nối.

Bạn có thể thay đổi hành vi này bằng cách chỉ định tổ hợp hai giá trị, được mô tả bên dưới:

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

IPC_CONNECT_WAIT_FOR_PORT – buộc lệnh gọi connect() phải chờ nếu cổng được chỉ định không tồn tại ngay lập tức tại thời điểm thực thi, thay vì ngay lập tức báo lỗi.

IPC_CONNECT_ASYNC – nếu được đặt, sẽ bắt đầu một kết nối không đồng bộ. Ứng dụng phải thăm dò ý kiến về tay điều khiển được trả về bằng cách gọi wait() cho sự kiện hoàn tất kết nối được chỉ báo bằng bit IPC_HANDLE_POLL_READY được đặt trong trường sự kiện của cấu trúc uevent_t trước khi bắt đầu hoạt động bình thường.

Messaging API

Các lệnh gọi API Messaging cho phép gửi và đọc tin nhắn qua một kết nối (kênh) đã thiết lập trước đó. Các lệnh gọi API Messaging giống nhau đối với máy chủ và ứng dụng.

Ứng dụng nhận được tên người dùng cho một kênh bằng cách đưa ra lệnh gọi connect() và máy chủ nhận được tên người dùng của kênh từ lệnh gọi accept() như mô tả ở trên.

Cấu trúc của thông báo Tin cậy

Như minh hoạ trong phần sau, các thông báo do Trusty API trao đổi có cấu trúc tối thiểu, để máy chủ và ứng dụng đồng ý về ngữ nghĩa của nội dung thực tế:

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

Một thông báo có thể bao gồm một hoặc nhiều vùng đệm không liền kề được biểu thị bằng một mảng cấu trúc iovec_t. Trusty thực hiện các thao tác đọc và ghi thu thập dữ liệu phân tán vào các khối này bằng cách sử dụng mảng iov. Nội dung của vùng đệm có thể được mô tả bằng mảng iov là hoàn toàn tuỳ ý.

Các phương thức trong Messaging API

send_msg()

Gửi thông báo qua một kênh đã chỉ định.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[trong] handle: Tên người dùng của kênh để gửi thông báo

[trong] msg: Con trỏ đến ipc_msg_t structure mô tả thông báo

[retval]: Tổng số byte được gửi khi thành công; nếu không thì sẽ là lỗi âm

Nếu ứng dụng (hoặc máy chủ) đang cố gửi tin nhắn qua kênh và không có không gian trong hàng đợi tin nhắn ngang hàng đích, thì kênh có thể chuyển sang trạng thái bị chặn gửi (điều này không bao giờ xảy ra với giao thức yêu cầu/trả lời đồng bộ đơn giản nhưng có thể xảy ra trong các trường hợp phức tạp hơn) được biểu thị bằng cách trả về mã lỗi ERR_NOT_ENOUGH_BUFFER. Trong trường hợp như vậy, phương thức gọi phải đợi cho đến khi máy ngang cấp giải phóng một số không gian trong hàng đợi nhận bằng cách truy xuất các thông báo xử lý và rút lui, được biểu thị bằng bit IPC_HANDLE_POLL_SEND_UNBLOCKED được đặt trong trường event của cấu trúc uevent_t do lệnh gọi wait() trả về.

get_msg()

Lấy siêu thông tin về thư tiếp theo trong hàng đợi thư đến

của một kênh cụ thể.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[trong] handle: Tên người dùng của kênh mà bạn phải truy xuất tin nhắn mới

[out] msg_info: Cấu trúc thông tin thông báo được mô tả như sau:

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

Mỗi thông báo được chỉ định một mã nhận dạng duy nhất trong tập hợp thông báo chưa xử lý và tổng chiều dài của mỗi thông báo được điền sẵn. Nếu được giao thức định cấu hình và cho phép, có thể có nhiều thông báo chưa xử lý (đã mở) cùng lúc cho một kênh cụ thể.

[retval]: NO_ERROR nếu thành công; lỗi âm nếu không

read_msg()

Đọc nội dung của thông báo có mã nhận dạng được chỉ định bắt đầu từ độ dời được chỉ định.

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

[trong] handle: Tên người dùng của kênh để đọc thư

[trong] msg_id: Mã nhận dạng của thư cần đọc

[trong] offset: Độ dời vào thư để bắt đầu đọc

[out] msg: Con trỏ đến cấu trúc ipc_msg_t mô tả một nhóm bộ đệm để lưu trữ dữ liệu thông báo đến

[retval]: Tổng số byte được lưu trữ trong vùng đệm msg khi thành công; nếu không thì sẽ là lỗi âm

Phương thức read_msg có thể được gọi nhiều lần bắt đầu từ một độ dời khác (không nhất thiết phải tuần tự) nếu cần.

put_msg()

Rút lại một thông báo có mã nhận dạng được chỉ định.

long put_msg(uint32_t handle, uint32_t msg_id);

[trong] handle: Tên người dùng của kênh đã nhận được thông báo

[trong] msg_id: Mã của thông báo sắp ngừng hoạt động

[retval]: NO_ERROR nếu thành công; lỗi âm nếu không

Không thể truy cập vào nội dung của thư sau khi thư đã ngừng hoạt động và vùng đệm mà thư đó chiếm đã được giải phóng.

API chỉ số mô tả tệp

API Chỉ số mô tả tệp bao gồm các lệnh gọi read(), write()ioctl(). Tất cả các lệnh gọi này đều có thể hoạt động trên một tập hợp các chỉ số mô tả tệp (static) được xác định trước (thường được biểu thị bằng các số nhỏ). Trong quá trình triển khai hiện tại, không gian chỉ số mô tả tệp tách biệt với không gian tay cầm IPC. API chỉ số mô tả tệp trong Trusty tương tự như API dựa trên chỉ số mô tả tệp truyền thống.

Theo mặc định, có 3 chỉ số mô tả tệp (tiêu chuẩn và phổ biến) được xác định trước:

  • 0 – đầu vào chuẩn. Việc triển khai mặc định của dữ liệu đầu vào chuẩn fd là không hoạt động (vì các ứng dụng đáng tin cậy không được dự kiến sẽ có bảng điều khiển tương tác), vì vậy, việc đọc, ghi hoặc gọi ioctl() trên fd 0 sẽ trả về lỗi ERR_NOT_SUPPORTED.
  • 1 – đầu ra chuẩn. Dữ liệu được ghi vào đầu ra chuẩn có thể được định tuyến (tuỳ thuộc vào cấp gỡ lỗi LK) đến UART và/hoặc nhật ký bộ nhớ có sẵn ở phía không an toàn, tuỳ thuộc vào nền tảng và cấu hình. Nhật ký gỡ lỗi và thông báo không quan trọng phải nằm trong đầu ra tiêu chuẩn. Các phương thức read()ioctl() không có tác dụng và sẽ trả về lỗi ERR_NOT_SUPPORTED.
  • 2 – sai số chuẩn. Dữ liệu được ghi vào lỗi chuẩn phải được định tuyến đến UART hoặc nhật ký bộ nhớ có sẵn ở phía không bảo mật, tuỳ thuộc vào nền tảng và cấu hình. Bạn chỉ nên ghi các thông báo quan trọng vào lỗi tiêu chuẩn, vì luồng này rất có thể sẽ không bị điều tiết. Các phương thức read()ioctl() không có tác dụng và sẽ trả về lỗi ERR_NOT_SUPPORTED.

Mặc dù có thể mở rộng tập hợp chỉ số mô tả tệp này để triển khai thêm fds (để triển khai các tiện ích dành riêng cho nền tảng), nhưng bạn cần thận trọng khi mở rộng chỉ số mô tả tệp. Việc mở rộng chỉ số mô tả tệp dễ gây ra xung đột và thường không được khuyến khích.

Các phương thức trong API chỉ số mô tả tệp

read()

Cố gắng đọc tối đa count byte dữ liệu từ một chỉ số mô tả tệp được chỉ định.

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

[trong] fd: Chỉ số mô tả tệp để đọc

[out] buf: Con trỏ đến vùng đệm để lưu trữ dữ liệu

[trong] count: Số byte tối đa cần đọc

[retval]: Số byte đã đọc được trả về; nếu không thì sẽ trả về lỗi âm

write()

Ghi tối đa count byte dữ liệu vào chỉ số mô tả tệp đã chỉ định.

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

[trong] fd: Chỉ số mô tả tệp cần ghi

[out] buf: Con trỏ đến dữ liệu cần ghi

[trong] count: Số byte tối đa cần ghi

[retval]: Số byte được ghi trả về; nếu không, lỗi âm

ioctl()

Gọi một lệnh ioctl đã chỉ định cho một chỉ số mô tả tệp nhất định.

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

[trong] fd: Chỉ số mô tả tệp để gọi ioctl()

[trong] cmd: Lệnh ioctl

[vào/ra] args: Con trỏ đến đối số ioctl()

API khác

Các phương thức trong API Khác

gettime()

Trả về thời gian hệ thống hiện tại (tính bằng nano giây).

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

[trong] clock_id: Tuỳ thuộc vào nền tảng; truyền giá trị 0 cho giá trị mặc định

[trong] flags: Dành riêng, phải bằng 0

[out] time: Con trỏ đến giá trị int64_t để lưu trữ thời gian hiện tại

[retval]: NO_ERROR nếu thành công; lỗi âm nếu không

nanosleep()

Tạm ngưng thực thi ứng dụng gọi trong một khoảng thời gian cụ thể và tiếp tục thực thi sau khoảng thời gian đó.

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

[trong] clock_id: Dành riêng, phải bằng 0

[trong] flags: Dành riêng, phải bằng 0

[trong] sleep_time: Thời gian ngủ tính bằng nano giây

[retval]: NO_ERROR nếu thành công; lỗi âm nếu không

Ví dụ về máy chủ ứng dụng đáng tin cậy

Ứng dụng mẫu sau đây cho thấy cách sử dụng các API ở trên. Mẫu này tạo một dịch vụ "echo" (hồi âm) xử lý nhiều kết nối đến và phản ánh lại cho phương thức gọi tất cả thông báo mà phương thức đó nhận được từ các ứng dụng bắt nguồn từ phía an toàn hoặc không an toàn.

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

# define MAX_ECHO_MSG_SIZE 64

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

static uint8_t msg_buf[MAX_ECHO_MSG_SIZE];

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

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

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

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

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

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

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

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

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

  return NO_ERROR;
}

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

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

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

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

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


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

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

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

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

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

Phương thức run_end_to_end_msg_test() gửi 10.000 thông báo không đồng bộ đến dịch vụ "echo" và xử lý các phản hồi.

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

Các ứng dụng và API không an toàn

Một nhóm dịch vụ Trusty, được phát hành từ phía bảo mật và được đánh dấu bằng thuộc tính IPC_PORT_ALLOW_NS_CONNECT, có thể truy cập vào các chương trình không gian hạt nhân và không gian người dùng chạy ở phía không bảo mật.

Môi trường thực thi ở phía không bảo mật (hạt nhân và không gian người dùng) khác biệt đáng kể so với môi trường thực thi ở phía bảo mật. Do đó, thay vì một thư viện duy nhất cho cả hai môi trường, sẽ có hai bộ API khác nhau. Trong nhân, API ứng dụng do trình điều khiển nhân trusty-ipc cung cấp và đăng ký một nút thiết bị ký tự mà các quy trình không gian người dùng có thể sử dụng để giao tiếp với các dịch vụ chạy ở phía bảo mật.

API ứng dụng Trusty IPC không gian người dùng

Thư viện API ứng dụng Trusty IPC là một lớp mỏng trên nút thiết bị fd.

Chương trình không gian người dùng bắt đầu một phiên giao tiếp bằng cách gọi tipc_connect(), khởi tạo kết nối với một dịch vụ Trusty được chỉ định. Trong nội bộ, lệnh gọi tipc_connect() sẽ mở một nút thiết bị được chỉ định để lấy chỉ số mô tả tệp và gọi lệnh gọi TIPC_IOC_CONNECT ioctl() với tham số argp trỏ đến một chuỗi chứa tên dịch vụ cần kết nối.

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

Chỉ có thể dùng chỉ số mô tả tệp thu được để giao tiếp với dịch vụ mà chỉ số mô tả tệp đó được tạo ra. Bạn nên đóng chỉ số mô tả tệp bằng cách gọi tipc_close() khi không cần kết nối nữa.

Chỉ số mô tả tệp thu được bằng lệnh gọi tipc_connect() hoạt động như một nút thiết bị ký tự thông thường; chỉ số mô tả tệp:

  • Có thể chuyển sang chế độ không chặn nếu cần
  • Có thể ghi vào bằng lệnh gọi write() tiêu chuẩn để gửi thông báo đến bên kia
  • Có thể được thăm dò ý kiến (sử dụng lệnh gọi poll() hoặc lệnh gọi select()) để biết trạng thái có sẵn của thông báo đến dưới dạng chỉ số mô tả tệp thông thường
  • Có thể đọc để truy xuất tin nhắn đến

Phương thức gọi gửi một thông báo đến dịch vụ Trusty bằng cách thực thi lệnh gọi ghi cho fd đã chỉ định. Tất cả dữ liệu được truyền đến lệnh gọi write() ở trên sẽ được trình điều khiển trusty-ipc chuyển đổi thành một thông báo. Thông báo được gửi đến phía bảo mật, nơi dữ liệu được xử lý bởi hệ thống con IPC trong hạt nhân Trusty, được định tuyến đến đích thích hợp và được gửi đến vòng lặp sự kiện của ứng dụng dưới dạng sự kiện IPC_HANDLE_POLL_MSG trên một tay điều khiển kênh cụ thể. Tuỳ thuộc vào giao thức cụ thể, dành riêng cho dịch vụ, dịch vụ Trusty có thể gửi một hoặc nhiều tin nhắn trả lời được phân phối lại cho bên không an toàn và được đặt vào hàng đợi tin nhắn chỉ số mô tả tệp kênh thích hợp để lệnh gọi read() của ứng dụng không gian người dùng truy xuất.

tipc_connect()

Mở một nút thiết bị tipc đã chỉ định và bắt đầu kết nối với một dịch vụ Trusty đã chỉ định.

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

[trong] dev_name: Đường dẫn đến nút thiết bị IPC đáng tin cậy cần mở

[trong] srv_name: Tên của một dịch vụ Trusty đã xuất bản để kết nối

[retval]: Chỉ số mô tả tệp hợp lệ nếu thành công, -1 nếu không.

tipc_close()

Đóng kết nối với dịch vụ Trusty do chỉ số mô tả tệp chỉ định.

int tipc_close(int fd);

[trong] fd: Chỉ số mô tả tệp đã được mở trước đó bằng lệnh gọi tipc_connect()

Kernel Trusty IPC Client API

API ứng dụng IPC Trusty của nhân hệ điều hành có sẵn cho các trình điều khiển nhân hệ điều hành. API Trusty IPC của không gian người dùng được triển khai trên API này.

Nhìn chung, cách sử dụng thông thường của API này bao gồm một phương thức gọi tạo đối tượng struct tipc_chan bằng cách sử dụng hàm tipc_create_channel(), sau đó sử dụng lệnh gọi tipc_chan_connect() để bắt đầu kết nối với dịch vụ Trusty IPC chạy ở phía bảo mật. Bạn có thể chấm dứt kết nối với phía từ xa bằng cách gọi tipc_chan_shutdown(), theo sau là tipc_chan_destroy() để dọn dẹp tài nguyên.

Khi nhận được thông báo (thông qua lệnh gọi lại handle_event()) rằng một kết nối đã được thiết lập thành công, phương thức gọi sẽ thực hiện những việc sau:

  • Lấy vùng đệm thông báo bằng lệnh gọi tipc_chan_get_txbuf_timeout()
  • Soạn một thông báo và
  • Thêm thông báo vào hàng đợi bằng phương thức tipc_chan_queue_msg() để phân phối đến một dịch vụ Trusty (ở phía bảo mật) mà kênh được kết nối

Sau khi xếp hàng thành công, phương thức gọi sẽ quên bộ đệm thông báo vì bộ đệm thông báo cuối cùng sẽ quay lại nhóm bộ đệm trống sau khi xử lý phía từ xa (để sử dụng lại sau này, cho các thông báo khác). Người dùng chỉ cần gọi tipc_chan_put_txbuf() nếu không thể đưa bộ đệm đó vào hàng đợi hoặc không cần thiết nữa.

Người dùng API nhận thông báo từ phía xa bằng cách xử lý lệnh gọi lại thông báo handle_msg() (được gọi trong ngữ cảnh của hàng đợi công việc trusty-ipc rx) cung cấp con trỏ đến vùng đệm rx chứa thông báo đến cần xử lý.

Dự kiến, việc triển khai lệnh gọi lại handle_msg() sẽ trả về con trỏ đến một struct tipc_msg_buf hợp lệ. Vùng đệm này có thể giống với vùng đệm tin nhắn đến nếu được xử lý cục bộ và không cần thiết nữa. Ngoài ra, vùng đệm này có thể là vùng đệm mới được lấy bằng lệnh gọi tipc_chan_get_rxbuf() nếu vùng đệm đến được đưa vào hàng đợi để xử lý thêm. Bạn phải theo dõi vùng đệm rx đã tách và cuối cùng là giải phóng vùng đệm đó bằng lệnh gọi tipc_chan_put_rxbuf() khi không còn cần đến nữa.

Các phương thức trong API ứng dụng IPC Kernel Trusty

tipc_create_channel()

Tạo và định cấu hình một thực thể của kênh IPC đáng tin cậy cho một thiết bị đáng tin cậy-ipc cụ thể.

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

[trong] dev: Con trỏ đến trusty-ipc mà kênh thiết bị được tạo

[trong] ops: Con trỏ đến struct tipc_chan_ops, với các lệnh gọi lại dành riêng cho phương thức gọi được điền sẵn

[trong] cb_arg: Con trỏ đến dữ liệu được truyền đến lệnh gọi lại tipc_chan_ops

[retval]: Con trỏ đến một thực thể mới tạo của struct tipc_chan nếu thành công, ERR_PTR(err) nếu không

Nói chung, phương thức gọi phải cung cấp hai lệnh gọi lại được gọi không đồng bộ khi hoạt động tương ứng đang diễn ra.

Sự kiện void (*handle_event)(void *cb_arg, int event) được gọi để thông báo cho phương thức gọi về thay đổi trạng thái kênh.

[trong] cb_arg: Con trỏ đến dữ liệu được truyền đến lệnh gọi tipc_create_channel()

[trong] event: Một sự kiện có thể là một trong các giá trị sau:

  • TIPC_CHANNEL_CONNECTED – cho biết đã kết nối thành công với bên từ xa
  • TIPC_CHANNEL_DISCONNECTED – cho biết bên từ xa đã từ chối yêu cầu kết nối mới hoặc yêu cầu ngắt kết nối cho kênh đã kết nối trước đó
  • TIPC_CHANNEL_SHUTDOWN – cho biết bên từ xa đang tắt, chấm dứt vĩnh viễn tất cả các kết nối

Lệnh gọi lại struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) được gọi để thông báo rằng một thông báo mới đã được nhận qua một kênh đã chỉ định:

  • [trong] cb_arg: Con trỏ đến dữ liệu được truyền đến lệnh gọi tipc_create_channel()
  • [trong] mb: Con trỏ đến struct tipc_msg_buf mô tả một tin nhắn đến
  • [retval]: Quá trình triển khai lệnh gọi lại dự kiến sẽ trả về một con trỏ đến struct tipc_msg_buf. Đây có thể là cùng một con trỏ được nhận dưới dạng tham số mb nếu thông báo được xử lý cục bộ và không còn bắt buộc nữa (hoặc có thể là vùng đệm mới nhận được qua lệnh gọi tipc_chan_get_rxbuf())

tipc_chan_connect()

Bắt đầu kết nối với dịch vụ IPC đáng tin cậy được chỉ định.

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

[trong] chan: Con trỏ đến một kênh do lệnh gọi tipc_create_chan() trả về

[trong] port: Con trỏ đến một chuỗi chứa tên dịch vụ cần kết nối

[retval]: 0 nếu thành công, lỗi âm nếu không

Phương thức gọi được thông báo khi kết nối được thiết lập bằng cách nhận lệnh gọi lại handle_event.

tipc_chan_shutdown()

Chấm dứt kết nối với dịch vụ Trusty IPC do lệnh gọi tipc_chan_connect() khởi tạo trước đó.

int tipc_chan_shutdown(struct tipc_chan *chan);

[trong] chan: Con trỏ đến một kênh do lệnh gọi tipc_create_chan() trả về

tipc_chan_destroy()

Huỷ bỏ một kênh IPC Trusty đã chỉ định.

void tipc_chan_destroy(struct tipc_chan *chan);

[trong] chan: Con trỏ đến một kênh do lệnh gọi tipc_create_chan() trả về

tipc_chan_get_txbuf_timeout()

Lấy vùng đệm thông báo có thể dùng để gửi dữ liệu qua một kênh đã chỉ định. Nếu vùng đệm không có sẵn ngay lập tức, thì phương thức gọi có thể bị chặn trong khoảng thời gian chờ đã chỉ định (tính bằng mili giây).

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

[trong] chan: Con trỏ đến kênh cần đưa thông báo vào hàng đợi

[trong] chan: Thời gian chờ tối đa để đợi cho đến khi có vùng đệm tx

[retval]: Vùng đệm thông báo hợp lệ khi thành công, ERR_PTR(err) khi gặp lỗi

tipc_chan_queue_msg()

Thêm một thông báo vào hàng đợi để gửi qua các kênh Trusty IPC được chỉ định.

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

[trong] chan: Con trỏ đến kênh để đưa thông báo vào hàng đợi

[trong] Con trỏ mb: đến thông báo cần đưa vào hàng đợi (thu được bằng lệnh gọi tipc_chan_get_txbuf_timeout())

[retval]: 0 nếu thành công, lỗi âm nếu không

tipc_chan_put_txbuf()

Giải phóng vùng đệm thông báo Tx đã chỉ định mà trước đó đã được lệnh gọi tipc_chan_get_txbuf_timeout() lấy.

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

[trong] chan: Con trỏ đến kênh chứa vùng đệm thông báo này

[trong] mb: Con trỏ đến vùng đệm thông báo để phát hành

[retval]: Không có

tipc_chan_get_rxbuf()

Lấy vùng đệm thông báo mới có thể dùng để nhận thông báo qua kênh đã chỉ định.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[trong] chan: Con trỏ đến kênh chứa vùng đệm thông báo này

[retval]: Vùng đệm thông báo hợp lệ khi thành công, ERR_PTR(err) khi gặp lỗi

tipc_chan_put_rxbuf()

Giải phóng vùng đệm thông báo đã chỉ định mà trước đó lệnh gọi tipc_chan_get_rxbuf() đã lấy.

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

[trong] chan: Con trỏ đến kênh chứa vùng đệm thông báo này

[trong] mb: Con trỏ đến vùng đệm thông báo để phát hành

[retval]: Không có