Tham chiếu API đáng tin cậy

Trusty cung cấp API để phát triển hai loại ứ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 bởi các ứng dụng đáng tin cậy

API Trusty thường mô tả hệ thống liên lạc giữa các quá trình (IPC) của Trusty, bao gồm cả liên lạc 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 Trusty để kết nối với các ứng dụng/dịch vụ đáng tin cậy và trao đổi tin nhắn tùy ý với chúng giống như dịch vụ mạng qua IP. Ứng dụng có trách nhiệm xác định định dạng dữ liệu và ngữ nghĩa của các tin nhắn này bằng giao thức cấp ứng dụng. Việc gửi tin nhắn đáng tin cậy đượ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à giao tiếp hoàn toàn không đồng bộ.

Cổng và kênh

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

Khi một 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 với một dịch vụ. Dịch vụ phải chấp nhận một kết nối đến và khi đó, nó cũng nhận được một kênh. Về bản chất, các cổng được sử dụng để tra cứu các dịch vụ và sau đó quá trình giao tiếp diễn ra qua một cặp kênh được kết nối (tức là các trường hợp kết nối trên một cổng). Khi một máy khách 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. Sử dụng đường dẫn song công hoàn toàn này, máy khách và máy chủ có thể trao đổi tin nhắn tùy ý cho đến khi một trong hai bên quyết định ngắt kết nối.

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

Tùy thuộc vào yêu cầu, một ứng dụng đáng tin cậy có thể vừa là máy khách vừa là 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ụ (với tư cách là máy chủ) có thể cần kết nối với các dịch vụ khác (với tư cách là khách hàng).

Xử lý API

Các thẻ điều khiển là các số nguyên không dấu biểu thị các tài nguyên như cổng và kênh, tương tự như các bộ mô tả tệp trong UNIX. Sau khi các thẻ điều khiển được tạo, chúng sẽ được đặt vào bảng thẻ điều khiển dành riêng cho ứng dụng và có thể được tham chiếu sau này.

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

Các phương thức trong API xử lý

Các thẻ điều khiển chỉ hợp lệ trong ngữ cảnh của một ứng dụng. Một ứng dụng không được chuyển giá trị của một điều khiển cho các ứng dụng khác trừ khi được chỉ định rõ ràng. Chỉ nên diễn giải một giá trị điều khiển bằng cách so sánh nó với INVALID_IPC_HANDLE #define, mà ứng dụng có thể sử dụng làm dấu hiệu cho thấy điều khiển không hợp lệ hoặc chưa được đặt.

Liên kết dữ liệu riêng tư do người gọi cung cấp với một thẻ điều khiển được chỉ định.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : Bất kỳ xử lý nào được trả về bởi một trong các lệnh gọi API

[in] cookie : Con trỏ tới dữ liệu không gian người dùng tùy ý trong ứng dụng Trusty

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

Cuộc gọi này hữu ích để xử lý các sự kiện khi chúng xảy ra sau đó sau khi thẻ điều khiển được tạo. Cơ chế xử lý sự kiện cung cấp phần xử lý và cookie của nó cho bộ xử lý sự kiện.

Các điều khiển có thể được chờ đợi cho các sự kiện bằng cách sử dụng lệnh gọi wait() .

Chờ đợi()

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

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

[in] handle_id : Bất kỳ mã điều khiển nào được trả về bởi một trong các lệnh gọi API

[out] event : Một con trỏ tới cấu trúc biểu thị một sự kiện đã xảy ra trên thẻ điều khiển này

[in] 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 sự kiện hợp lệ xảy ra trong khoảng thời gian chờ được chỉ định; ERR_TIMED_OUT nếu đã hết thời gian chờ được chỉ định 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 vào cấu trúc uevent_t được chỉ định với 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 sự kết hợp của 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 - tùy thuộc vào loại tay cầm, như sau:

  • Đối với các 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 kết nối không đồng bộ (xem connect() ) đã được thiết lập

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

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

Trình xử lý sự kiện phải được chuẩn bị để xử lý sự kết hợp của 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ó các tin nhắn đang chờ xử lý và một kết nối bị đóng bởi một thiết bị ngang hàng cùng một lúc.

Hầu hết các sự kiện đều dính. Chúng 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ả các tin nhắn đ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ý). Ngoại lệ là trường hợp sự kiện IPC_HANDLE_POLL_SEND_UNBLOCKED , sự kiện này sẽ bị xóa khi đọc và ứng dụng chỉ có một cơ hội để xử lý nó.

Các tay cầm có thể bị hủy bằng cách gọi phương thức close() .

đóng()

Phá hủy tài nguyên được liên kết với tay cầm đã chỉ định và xóa nó khỏi bảng tay cầm.

long close(uint32_t handle_id);

[in] handle_id : Xử lý để tiêu diệt

[retval]: 0 nếu thành công; một lỗi tiêu cực nếu không

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 các điểm cuối dịch vụ của nó. Mỗi cổng được đại diện bởi 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)

[in] 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 toàn hệ thống; nỗ lực tạo bản sao sẽ thất bại.

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

[in] 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 tin nhắn tối đa mà bạn có thể trao đổi với các thiết bị ngang hàng.

flags [in]: Sự kết hợp của các cờ chỉ định hành vi cổng bổ sung

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 bảo mật 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 âm hoặc lỗi cụ thể nếu âm

Sau đó, máy chủ sẽ thăm dò danh sách các cổng xử lý 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 mã điều khiển khác) để sau đó có thể thăm dò các tin nhắn đến .

chấp nhận()

Chấp nhận một kết nối đến và xử lý một kênh.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id : Xử lý đại diện cho cổng mà máy khách đã kết nối

[out] peer_uuid : Con trỏ tới cấu trúc uuid_t để chứa UUID của ứng dụng khách đang 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]: Xử lý một kênh (nếu không âm) mà trên đó máy chủ có thể trao đổi tin nhắn với máy khách (hoặc mã lỗi khác)

API ứng dụng khách

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

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

kết nối()

Bắt đầu kết nối đến một cổng được chỉ định theo tên.

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

path [in] : Tên của cổng được xuất bản bởi ứng dụng Trusty

flags [in] : Chỉ định hành vi bổ sung, tùy chọn

[retval]: Xử lý kênh mà tin nhắn có thể được trao đổi với máy chủ; lỗi nếu tiêu cực

Nếu không có flags nào được chỉ định (tham số flags được đặt thành 0), việc gọi connect() sẽ khởi tạo kết nối đồng bộ đến một cổng được chỉ định và ngay lập tức trả về lỗi 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. .

Hành vi này có thể được thay đổi bằng cách chỉ định kế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 khi thực thi, thay vì thất bại ngay lập tức.

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

API nhắn tin

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

Máy khách nhận được điều khiển kênh bằng cách thực hiện lệnh gọi connect() và máy chủ nhận điều khiển kênh từ lệnh gọi accept() , được mô tả ở trên.

Cấu trúc của một tin nhắn Trusty

Như được trình bày dưới đây, các tin nhắn được trao đổi bởi API Trusty có cấu trúc tối thiểu, để 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 bộ đệ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 việc đọc và ghi thu thập 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 tùy ý.

Các phương thức trong API nhắn tin

gửi_msg()

Gửi tin nhắn qua một kênh được chỉ định.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : Xử lý kênh để gửi tin nhắn

[in] msg : Con trỏ tới ipc_msg_t structure mô tả thông báo

[retval]: Tổng số byte được gửi thành công; một lỗi tiêu cực nếu không

Nếu máy khách (hoặc máy chủ) đang cố gửi tin nhắn qua kênh và không còn chỗ trống trong hàng đợi tin nhắn ngang hàng đích thì kênh có thể chuyển sang trạng thái chặn gửi (điều này sẽ không bao giờ xảy ra đối 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 những 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, người gọi phải đợi cho đến khi thiết bị ngang hàng giải phóng một số khoảng trống trong hàng đợi nhận của nó bằng cách truy xuất các tin nhắn 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 được trả về bởi lệnh gọi wait() .

get_msg()

Nhận thông tin meta về tin nhắn tiếp theo trong hàng đợi tin nhắn đến

của một kênh xác định.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : Xử lý kênh mà tin nhắn mới phải được truy xuất

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

[retval]: NO_ERROR khi thành công; một lỗi tiêu cực nếu không

read_msg()

Đọc nội dung của tin nhắn với ID được chỉ định bắt đầu từ offset được chỉ định.

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

[in] handle : Xử lý kênh để đọc tin nhắn

[in] msg_id : ID của tin nhắn cần đọc

[in] offset : Offset vào tin nhắn để bắt đầu đọc

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

[retval]: Tổng số byte được lưu trữ trong bộ đệm msg khi thành công; một lỗi tiêu cực nếu không

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

put_msg()

Hủy bỏ một tin nhắn có ID được chỉ định.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Xử lý kênh mà tin nhắn đã đến

[in] msg_id : ID của tin nhắn đang bị gỡ bỏ

[retval]: NO_ERROR khi thành công; một lỗi tiêu cực nếu không

Không thể truy cập nội dung tin nhắn sau khi tin nhắn đã bị gỡ bỏ và bộ đệm mà nó chiếm giữ đã được giải phóng.

API mô tả tệp

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

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

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

Mặc dù bộ mô tả tệp này có thể được mở rộng để triển khai nhiều fds hơn (để triển khai các phần mở rộng dành riêng cho nền tảng), việc mở rộng các bộ mô tả tệp cần phải được thực hiện một cách thận trọng. Việc mở rộng bộ mô tả tệp có xu hướng tạo ra xung đột và thường không được khuyến khích.

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

đọc()

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

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

[in] fd : Bộ mô tả tệp để đọc

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

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

[retval]: Trả về số byte đã đọc; một lỗi tiêu cực nếu không

viết()

Ghi để count byte dữ liệu vào bộ mô tả tệp được chỉ định.

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

[in] fd : Bộ mô tả tệp để ghi vào

[out] buf : Con trỏ tới dữ liệu cần ghi

[in] count : Số byte tối đa để ghi

[retval]: Trả về số byte đã ghi; một lỗi tiêu cực nếu không

ioctl()

Gọi lệnh ioctl được chỉ định cho một bộ mô tả tệp nhất định.

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

[in] fd : Bộ mô tả tệp để gọi ioctl()

[in] cmd : Lệnh ioctl

[in/out] args : Con trỏ tới đối số ioctl()

API khác

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

dành thời gian()

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

[in] clock_id : Phụ thuộc vào nền tảng; chuyển số 0 thành mặc định

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

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

[retval]: NO_ERROR khi thành công; một lỗi tiêu cực nếu không

nanosleep()

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

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

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

[in] flags : Dành riêng, 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; một lỗi tiêu cực 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 tạo ra một dịch vụ "echo" 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 mà nó nhận được từ máy khách có nguồn gốc 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 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 tin nhắn không đồng bộ đến dịch vụ "echo" và xử lý các câu 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 đối với các chương trình 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 (kernel 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 an toàn. Do đó, thay vì một thư viện duy nhất cho cả hai môi trường, có hai bộ API khác nhau. Trong kernel, API máy khách được cung cấp bởi trình điều khiển kernel đáng tin cậy-ipc và đăng ký 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 để liên lạc với các dịch vụ chạy ở phía bảo mật.

Không gian người dùng API máy khách IPC đáng tin cậy

Không gian người dùng Thư viện Trusty IPC Client API là một lớp mỏng phía trên nút thiết bị fd .

Chương trình không gian người dùng bắt đầu phiên giao tiếp bằng cách gọi tipc_connect() , khởi tạo kết nối đến 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 bộ 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ụ để kết nối.

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

Bộ mô tả tệp kết quả chỉ có thể được sử dụng để liên lạc với dịch vụ mà nó được tạo. Bộ mô tả tệp phải được đóng bằng cách gọi tipc_close() khi không cần kết nối nữa.

Bộ 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ự điển hình; bộ mô tả tập tin:

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

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 chuyển đến lệnh write() ở trên sẽ được trình điều khiển Trusty-ipc chuyển thành tin nhắn. Thông báo được gửi đến bên bảo mật nơi dữ liệu được xử lý bởi hệ thống con IPC trong lõi Trusty và được định tuyến đến đích thích hợp và được gửi đến vòng lặp sự kiện ứng dụng dưới dạng sự kiện IPC_HANDLE_POLL_MSG trên một bộ điều khiển kênh cụ thể. Tùy 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 gửi trở lại phía không bảo mật và được đặt trong hàng đợi tin nhắn mô tả tệp kênh thích hợp để ứng dụng không gian người dùng read() gọi.

tipc_connect()

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

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

[in] dev_name : Đường dẫn tới node thiết bị Trusty IPC để mở

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

[retval]: Bộ 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 được chỉ định bởi bộ mô tả tệp.

int tipc_close(int fd);

[in] fd : Bộ mô tả tệp được mở trước đó bằng lệnh gọi tipc_connect()

API máy khách IPC đáng tin cậy của hạt nhân

API máy khách Trusty IPC của kernel có sẵn cho trình điều khiển kernel. Không gian người dùng API Trusty IPC được triển khai dựa trên API này.

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

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

  • Lấy bộ đệm tin nhắn bằng lệnh gọi tipc_chan_get_txbuf_timeout()
  • Soạn tin nhắn và
  • Xếp hàng tin nhắn bằng phương thức tipc_chan_queue_msg() để gửi đến dịch vụ Trusty (về mặt bảo mật) mà kênh được kết nối

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

Người dùng API nhận tin nhắn 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 rx đáng tin cậy) cung cấp con trỏ tới bộ đệm rx chứa tin nhắn đến cần xử lý.

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

Các phương thức trong API máy khách IPC đáng tin cậy của hạt nhân

tipc_create_channel()

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

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

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

[in] ops : Con trỏ tới một struct tipc_chan_ops , với các lệnh gọi lại dành riêng cho người gọi được điền vào

[in] cb_arg : Con trỏ tới dữ liệu sẽ được chuyển đến lệnh gọi lại tipc_chan_ops

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

Nói chung, người 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 diễn ra.

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

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

[in] 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 phía từ 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 đối với 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 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 để cung cấp thông báo rằng một tin nhắn mới đã được nhận qua một kênh được chỉ định:

  • [in] cb_arg : Con trỏ tới dữ liệu được truyền tới lệnh gọi tipc_create_channel()
  • [in] mb : Con trỏ tới struct tipc_msg_buf mô tả tin nhắn đến
  • [retval]: Việc triển khai gọi lại dự kiến ​​sẽ trả về một con trỏ tới struct tipc_msg_buf 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 thiết nữa (hoặc nó có thể là bộ đệm mới được lấy bởi cuộc gọi tipc_chan_get_rxbuf() )

tipc_chan_connect()

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

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

[in] chan : Con trỏ tới kênh được trả về bởi lệnh gọi tipc_create_chan()

[in] port : Con trỏ tới 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, ngược lại là lỗi âm

Người gọi sẽ đượ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 được khởi tạo trước đó bằng lệnh gọi tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : Con trỏ tới kênh được trả về bởi lệnh gọi tipc_create_chan()

tipc_chan_destroy()

Phá hủy kênh IPC đáng tin cậy được chỉ định.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : Con trỏ tới kênh được trả về bởi lệnh gọi tipc_create_chan()

tipc_chan_get_txbuf_timeout()

Lấy bộ đệm tin nhắn có thể được sử dụng để gửi dữ liệu qua một kênh được chỉ định. Nếu bộ đệm không có sẵn ngay lập tức, người gọi có thể bị chặn trong 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);

[in] chan : Con trỏ tới kênh để xếp hàng tin nhắn

[in] chan : Thời gian chờ tối đa để đợi cho đến khi bộ đệm tx khả dụng

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

tipc_chan_queue_msg()

Xếp hàng tin nhắn sẽ được 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);

[in] chan : Con trỏ tới kênh để xếp hàng tin nhắn

[in] mb: Con trỏ tới tin nhắn cần xếp hàng (thu được bằng lệnh gọi tipc_chan_get_txbuf_timeout() )

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

tipc_chan_put_txbuf()

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

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

[in] chan : Con trỏ tới kênh chứa bộ đệm tin nhắn này

[in] mb : Con trỏ tới bộ đệm tin nhắn cần giải phóng

[retval]: Không có

tipc_chan_get_rxbuf()

Lấy bộ đệm tin nhắn mới có thể được sử dụng để nhận tin nhắn qua kênh được chỉ định.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : Con trỏ tới kênh chứa bộ đệm tin nhắn này

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

tipc_chan_put_rxbuf()

Giải phóng bộ đệm thông báo đã chỉ định trước đó có được bằng lệnh gọi tipc_chan_get_rxbuf() .

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

[in] chan : Con trỏ tới kênh chứa bộ đệm tin nhắn này

[in] mb : Con trỏ tới bộ đệm tin nhắn để giải phóng

[retval]: Không có