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

Trusty cung cấp các API để phát triển 2 lớp ứng dụng/dịch vụ:

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

Đáng tin cậy API thường mô tả hệ thống giao tiếp liên quy trình (IPC) đáng tin cậy, bao gồm cả việc giao tiếp với thế giới không an toàn. Phần mềm đang chạy trên bộ xử lý chính có thể dùng Trusty API để kết nối với các ứng dụng/dịch vụ đáng tin cậy và trao đổi tin nhắn tuỳ ý với họ theo cách giống như một dịch vụ mạng qua IP. Việc xác định định dạng dữ liệu và ngữ nghĩa của những dữ liệu này là tuỳ thuộc vào ứng dụng bằng giao thức cấp ứng dụng. Dịch vụ gửi tin nhắn đáng tin cậy là được đảm bảo bởi cơ sở hạ tầng Trusty cơ bản (dưới 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 ứng dụng Trusty dùng cổng để hiển thị điểm cuối của dịch vụ trong biểu mẫu của đường dẫn được đặt tên mà ứng dụng kết nối. Điều này mang lại một giá trị đơn giản, dựa trên chuỗi mã dịch vụ để khách hàng sử dụng. Quy ước đặt tên là kiểu DNS ngược đặt tên, ví dụ: com.google.servicename.

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

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

Tuỳ thuộc vào yêu cầu, một ứng dụng đáng tin cậy có thể là ứng dụng vừa là ứng dụng máy chủ cùng một lúc. Một ứng dụng đáng tin cậy xuất bản 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).

API xử lý

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

Người gọi có thể liên kết dữ liệu cá nhân 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 Xử lý

Tên người dùng chỉ hợp lệ trong ngữ cảnh của một ứng dụng. Ứng dụng nên không truyền giá trị của tên người dùng sang các ứng dụng khác trừ phi một cách rõ ràng đã chỉ định. Bạn chỉ nên diễn giải giá trị tên người dùng bằng cách so sánh với INVALID_IPC_HANDLE #define, mà ứng dụng có thể dùng làm cho biết tên người dùng 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: Bất kỳ tên người dùng nào được 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 khi thành công, nếu không thì < 0 mã lỗi

Lệnh gọi này rất hữu ích khi xử lý các sự kiện xảy ra sau này tên người dùng đã được tạo. Cơ chế xử lý sự kiện cung cấp tay điều khiển và cookie của cookie đó trở lại trình xử lý sự kiện.

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

chờ()

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

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

[trong] handle_id: Bất kỳ tên người dùng nào được một trong các lệnh gọi API trả về

[out] event: Con trỏ trỏ đến cấu trúc biểu thị một sự kiện đã xảy ra trên tên người dùng này

[in] timeout_msecs: Giá trị thời gian chờ tính bằng mili giây; một 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 một khoảng thời gian chờ chỉ định; ERR_TIMED_OUT nếu thời gian chờ được chỉ định đã trôi qua nhưng không sự kiện đã xảy ra; < 0 để xem các lỗi khác

Sau khi thành công (retval == NO_ERROR), lệnh gọi wait() lấp đầy cấu trúc uevent_t được chỉ định bằng thông tin về sự kiện đã xảy ra.

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ý, phương thức gọi sẽ 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 – phụ 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 kênh, giá trị này cho biết rằng kết nối không đồng bộ (xem connect()) được thành lập

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

  • IPC_HANDLE_POLL_HUP – cho biết rằng một kênh đã bị đóng bởi một ứng dụng ngang hàng
  • IPC_HANDLE_POLL_MSG – cho biết rằng 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 trước đó người gọi bị chặn gửi có thể cố gửi thông báo lại (xem mô tả của send_msg() để biết chi tiết)

Trình xử lý sự kiện cần được chuẩn bị để xử lý tổ hợp các giá trị vì nhiều bit có thể được đặt cùng một lúc. Ví dụ: để có một có thể có tin nhắn đang chờ xử lý và kết nối bị đóng bởi ứng dụng ngang hàng cùng một lúc.

Hầu hết các sự kiện đều cố định. Chúng vẫn tồn tại miễn là điều kiện cơ bản vẫn tồn tại (ví dụ: đã nhận được tất cả tin nhắn đang chờ xử lý và đang chờ kết nối được xử lý). Trường hợp ngoại lệ là sự kiện IPC_HANDLE_POLL_SEND_UNBLOCKED bị xoá sau khi đọc và ứng dụng chỉ có một cơ hội để xử lý nó.

Bạn có thể huỷ tên người dùng bằng cách gọi phương thức close().

đóng()

Huỷ bỏ tài nguyên liên kết với tên người dùng được chỉ định và xoá tài nguyên đó khỏi bảng điều khiển.

long close(uint32_t handle_id);

[in] handle_id: Ô điều khiển để huỷ

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

API máy chủ

Một 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 điểm cuối dịch vụ của nó. Mỗi cổng được biểu thị bằng một tên người dùng.

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

cổng_tạo()

Tạo một cổng dịch vụ đã đặ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). Chiến dịch này tên phải là duy nhất trên hệ thống; sẽ không thể tạo bản sao.

[in] num_recv_bufs: Số vùng đệm tối đa mà một kênh đang bật cổng này có thể phân bổ trước để tạo điều kiện trao đổi dữ liệu với máy khách. Đếm vùng đệm riêng biệt cho dữ liệu theo cả hai hướng, vì vậy việc chỉ định 1 ở đây sẽ có nghĩa là 1 vùng đệm gửi và 1 nhận được phân bổ trước. Nhìn chung, số vùng đệm 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ố này có thể chỉ là 1 trong trường hợp một giao thức rất đồng bộ (gửi tin nhắn, nhận thư trả lời trước khi gửi tin nhắn khác). Tuy nhiên, con số này có thể nhiều hơn nếu khách hàng muốn gửi nhiều hơn một tin nhắn trước khi một tin nhắn trả lời có thể xuất hiện (ví dụ: một tin nhắn dưới dạng phần mở đầu và một tin nhắn khác dưới dạng lệnh thực tế). Chiến lược phát hành đĩa đơn các tập hợp bộ đệm được phân bổ là cho mỗi kênh, do đó, có hai kết nối (kênh) riêng biệt sẽ có những tập hợp bộ đệm riêng.

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

[in] flags: Tổ hợp cờ chỉ định hành vi bổ sung của cổng

Giá trị này phải là sự kế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 an toàn khác

IPC_PORT_ALLOW_NS_CONNECT – cho phép kết nối từ thế giới không an toàn

[retval]: Xử lý cổng được tạo nếu không có âm hoặc có một lỗi cụ thể nếu phủ định

Sau đó, máy chủ thăm dò danh sách các cổng xử lý cho các kết nối đến thông qua lệnh gọi wait(). Sau khi nhận được kết nối yêu cầu đượ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, trường máy chủ nên 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 tên người dùng khác) mà sau đó có thể được thăm dò ý kiến về các thư đến.

chấp nhận()

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

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[trong] handle_id: Tên người dùng biểu thị cổng mà ứng dụng đã kết nối

[out] peer_uuid: Con trỏ đến cấu trúc uuid_t cần được điền mã nhận dạng duy nhất (UUID) của ứng dụng khách kết nối. Nó sẽ được đặt thành tất 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 người dùng cho một kênh (nếu không âm) mà máy chủ có thể trao đổi tin nhắn với máy khách (hoặc mã lỗi)

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

kết nối()

Bắt đầu 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 cổng do một ứng dụng Trusty xuất bản

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

[retval]: Tên người dùng cho một kênh mà qua đó người dùng có thể trao đổi tin nhắn với máy chủ; lỗi nếu giá trị âm

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

Hành vi này có thể được thay đổi 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 connect() lệnh gọi chờ nếu cổng được chỉ định không tồn tại ngay khi thực thi, thay vì không thành công ngay lập tức.

IPC_CONNECT_ASYNC – nếu được đặt, sẽ bắt đầu một kết nối không đồng bộ. Một ứng dụng phải thăm dò ý kiến xử lý được trả về (bằng cách gọi wait() cho một sự kiện hoàn thành kết nối được biểu thị bởi IPC_HANDLE_POLL_READY bit đượ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.

API Nhắn tin

Lệnh gọi Messaging API cho phép gửi và đọc tin nhắn qua kết nối đã được thiết lập trước đó (kênh). Lệnh gọi Messaging API là đối với máy chủ và máy khách.

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

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

Như minh hoạ sau đây, các thông báo do Trusty API trao đổi có cấu trúc này cho máy chủ và máy khách để thống nhất 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 gồm các cấu trúc iovec_t. Trusty thực hiện hoạt động thu thập phân tán đọc và ghi 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ể mô tả được theo mảng iov là hoàn toàn tuỳ ý.

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

gửi_tin_nhắn()

Gửi tin nhắn qua một kênh cụ thể.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[trong] handle: Tên người dùng cho kênh mà bạn dùng để gửi thông báo

[in] msg: Con trỏ đến ipc_msg_t structure mô tả tin nhắn

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

Nếu máy khách (hoặc máy chủ) đang cố gửi thông báo qua kênh và không có khoảng trống trong hàng đợi thông báo ngang hàng đích, thì kênh có thể nhập trạng thái bị chặn gửi (điều này không bao giờ xảy ra đối với một giao dịch đồng bộ đơn giản giao thức yêu cầu/trả lời nhưng có thể xảy ra trong các trường hợp phức tạp hơn). được chỉ định bằng cách trả về một mã lỗi ERR_NOT_ENOUGH_BUFFER. Trong trường hợp này, phương thức gọi phải đợi cho đến khi ứng dụng ngang hàng giải phóng một số trong hàng đợi nhận bằng cách truy xuất thông báo xử lý và gỡ bỏ, đượ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 được lệnh gọi wait() trả về.

get_msg()

Lấy thông tin meta 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 tin nhắn đượ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ư được gán một mã nhận dạng duy nhất trong tập hợp các thư chưa gửi, và tổng thời lượng của từng thông báo được điền. Nếu được định cấu hình và cho phép bởi có thể có nhiều thư chưa xử lý (đã mở) cùng một lúc cho kênh cụ thể.

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

đọc_msg()

Đọc nội dung thông báo với ID được chỉ định bắt đầu từ chênh lệch đượ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 mà bạn dùng để đọc thông báo

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

[in] offset: Bù trừ vào thư mà từ đó bắt đầu đọc

[out] msg: Con trỏ đến cấu trúc ipc_msg_t mô tả một tập hợp vùng đệm để lưu trữ tin nhắn đến dữ liệu

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

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

put_msg()

Huỷ bỏ thư có mã nhận dạng đã chỉ định.

long put_msg(uint32_t handle, uint32_t msg_id);

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

[in] msg_id: Mã nhận dạng của thông báo sẽ bị gỡ bỏ

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

Bạn không thể truy cập nội dung thông báo sau khi thông báo đã bị gỡ bỏ và vùng đệm mà nó chiếm giữ đã được giải phóng.

API Mô tả tệp

File Descriptor API bao gồm read(), write(), và ioctl() cuộc gọi. Tất cả các lệnh gọi này có thể hoạt động trên một tập hợp tệp (tĩnh) được xác định trước tên mô tả được biểu thị theo cách truyền thống bằng các số nhỏ. Hiện tại thì không gian mô tả tệp tách biệt với tên người dùng IPC. . File Descriptor API trong Trusty là 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 được xác định trước (tiêu chuẩn và phổ biến):

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

Mặc dù tập hợp chỉ số mô tả tệp này có thể được mở rộng để triển khai thêm fds (để triển khai các phần mở rộng dành riêng cho nền tảng), mở rộng nhu cầu của chỉ số mô tả tệp cần được thận trọng. Dễ dàng tạo mở rộng chỉ số mô tả tệp xung đột và thường không được khuyến nghị.

Các phương thức trong File Descriptor API

phương thức đọc()

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 mà bạn muốn đọc

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

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

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

ghi()

Ghi tối đa count byte dữ liệu vào chỉ số mô tả tệp được 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

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

[retval]: Số byte trả về đã ghi; nếu không thì lỗi âm

hà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 cần gọi ioctl()

[in] cmd: Lệnh ioctl

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

API khác

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

gettime()

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

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

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

[in] flags: Đặt trước, phải bằng 0

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

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

ngủ nano()

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

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

[in] clock_id: Đặt trước, phải bằng 0

[in] flags: Đặt trước, phải bằng 0

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

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

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

Ứng dụng mẫu sau đây minh hoạ cách sử dụng các API ở trên. Mẫu tạo ra "tiếng vọng" dịch vụ này xử lý nhiều kết nối đến và phản ánh lại cho người gọi tất cả tin nhắn nhận được từ ứng dụng khách từ phía bảo mật hoặc phía không bảo mật.

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

Phương thức run_end_to_end_msg_test() gửi 10.000 thông báo không đồng bộ cho "tiếng vọng" dịch vụ và tên người dùng trả lờ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 thế giới không an toàn

Một tập hợp các dịch vụ Trusty, được xuất bản 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 được vào nhân và các chương trình trong không gian người dùng chạy trên không an toàn.

Môi trường thực thi ở phía không bảo mật (không gian nhân hệ điều hành 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 cho cả hai môi trường, có hai các tập hợp API khác nhau. Trong nhân, API Ứng dụng được cung cấp bởi trình điều khiển hạt nhân đáng tin cậy-ipc và đăng ký một nút thiết bị ký tự có thể được sử dụng bằng các quy trình không gian người dùng để giao tiếp với các dịch vụ chạy trên ở bên.

API ứng dụng IPC đáng tin cậy trong không gian người dùng

Thư viện API ứng dụng IPC đáng tin cậy không gian của người dùng là một lớp mỏng ở trên cùng của nút thiết bị fd.

Một chương trình trong 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 đã chỉ định. Trong nội bộ, lệnh gọi tipc_connect() sẽ mở một nút thiết bị đã chỉ định để lấy chỉ số mô tả tệp và gọi TIPC_IOC_CONNECT ioctl() lệnh gọi có 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à ứng dụng được tạo. Chỉ số mô tả tệp nên được đóng bằng đang gọi tipc_close() khi không cần kết nối nữa.

Chỉ số mô tả tệp thu được qua 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ể được ghi vào bằng write() tiêu chuẩn gọi để gửi tin nhắn cho bên kia
  • Có thể thăm dò ý kiến (sử dụng poll() cuộc gọi hoặc select() cuộc gọi) về tính sẵn có của thư đế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

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

tipc_connect()

Mở một nút thiết bị tipc đã chỉ định và bắt đầu một kết nối với dịch vụ Trusty được 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 để mở

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

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

tipc_close()

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

int tipc_close(int fd);

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

API ứng dụng IPC đáng tin cậy hạt nhân

Kernel Trusty IPC Client API khả dụng cho trình điều khiển nhân. Người dùng space Trusty IPC API được triển khai dựa 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 việc tạo một phương thức gọi đối tượng struct tipc_chan bằng cách sử dụng tipc_create_channel() rồi sử dụng lệnh gọi tipc_chan_connect() để bắt đầu một kết nối với dịch vụ IPC đáng tin cậy đang chạy trên mạng bảo mật ở bên. Có thể chấm dứt kết nối với phía từ xa bằng đang gọi tipc_chan_shutdown(), sau đó là tipc_chan_destroy() để dọn dẹp tài nguyên.

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

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

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

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

Theo dự kiến, lệnh gọi lại handle_msg() phương thức triển khai này sẽ trả về một con trỏ đến struct tipc_msg_buf hợp lệ. Nó 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, đó có thể là một vùng đệm mới lấy được 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. Phải theo dõi một vùng đệm rx đã tách và cuối cùng được phát hành bằng lệnh gọi tipc_chan_put_rxbuf() khi dữ liệu đó không còn cần thiết nữa.

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

tipc_create_channel()

Tạo và định cấu hình bản sao của kênh IPC đáng tin cậy cho một kênh cụ thể thiết bị đáng tin cậy ipc của chúng tôi.

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

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

[trong] ops: Con trỏ đến struct tipc_chan_ops, với người gọi cụ thể đã điền các lệnh gọi lại

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

[retval]: Con trỏ tới một phiên bản mới được tạo của struct tipc_chan chúc mừng thành công! Nếu không thì ERR_PTR(err)

Nói chung, phương thức gọi phải cung cấp 2 lệnh gọi lại được gọi không đồng bộ khi hoạt động tương ứng 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 Cuộc gọi tipc_create_channel()

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

  • TIPC_CHANNEL_CONNECTED – cho biết đã kết nối thành công ở phía xa
  • TIPC_CHANNEL_DISCONNECTED – cho biết phía 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 của kênh đã kết nối trước đó
  • TIPC_CHANNEL_SHUTDOWN – cho biết phía từ xa đang tắt, chấm dứt vĩnh viễn mọi kết nối

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) lệnh gọi lại được gọi để đưa ra thông báo rằng một tin nhắn mới đã được gọi nhận được trong kênh cụ thể:

  • [trong] cb_arg: Con trỏ đến dữ liệu được truyền đến Cuộc gọi tipc_create_channel()
  • [trong] mb: Con trỏ đến struct tipc_msg_buf mô tả một thư đến
  • [retval]: Việc triển khai lệnh gọi lại dự kiến sẽ trả về một con trỏ đến một struct tipc_msg_buf có thể giống với con trỏ nhận được với tư cách là Tham số mb nếu thư được xử lý cục bộ và không phải là bắt buộc nữa (hoặc đơn vị quảng cáo đó) có thể là vùng đệm mới lấy được từ lệnh gọi tipc_chan_get_rxbuf())

tipc_chan_connect()

Bắt đầu kết nối với dịch vụ Trusty IPC đã chỉ định.

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

[trong] chan: Con trỏ đến một kênh do Cuộc gọi tipc_create_chan()

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

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

Phương thức gọi sẽ được thông báo khi kết nối được thiết lập bằng việc 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ụ IPC đáng tin cậy đã khởi tạo trước đó bằng một lệnh gọi tipc_chan_connect().

int tipc_chan_shutdown(struct tipc_chan *chan);

[trong] chan: Con trỏ đến kênh được trả về bởi một cuộc gọi tipc_create_chan()

tipc_chan_cancel()

Huỷ bỏ một kênh IPC đáng tin cậy được chỉ định.

void tipc_chan_destroy(struct tipc_chan *chan);

[trong] chan: Con trỏ đến một kênh do Cuộc gọi tipc_create_chan()

tipc_chan_get_txbuf_timeout()

Nhận vùng đệm thông báo có thể dùng để gửi dữ liệu qua một vùng đệm thông báo được chỉ định của bạn. Nếu vùng đệm không có sẵn ngay thì phương thức gọi có thể bị chặn cho thời gian chờ được 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 sẽ đưa thư vào hàng đợi

[in] chan: Thời gian chờ tối đa phải chờ cho đến Có thể sử dụng tx vùng đệm

[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()

Xếp hàng thư để gửi trong Các kênh IPC đáng tin cậy.

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

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

[trong] mb: Con trỏ đến thư để đưa vào danh sách chờ (nhận được qua cuộc gọi tipc_chan_get_txbuf_timeout())

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

tipc_chan_put_txbuf()

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

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

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

[in] mb: Con trỏ đến vùng đệm thông báo để giải phóng

[retval]: Không có

tipc_chan_get_rxbuf()

Nhận vùng đệm thông báo mới có thể dùng để nhận thông báo qua kênh cụ thể.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[trong] chan: Con trỏ đến kênh có 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 do một Cuộc gọi tipc_chan_get_rxbuf().

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

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

[in] mb: Con trỏ đến vùng đệm thông báo để giải phóng

[retval]: Không có