Trusty cung cấp các API để phát triển hai 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 bình 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
Trusty API mô tả chung về hệ thống thông tin liên lạc giữa các quy trình (IPC) Trusty, bao gồm cá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 các API Trusty để kết nối với các ứng dụng / dịch vụ đáng tin cậy và trao đổi các thông điệp tùy ý với chúng giống như một dịch vụ mạng qua IP. Ứng dụng quyết định định dạng dữ liệu và ngữ nghĩa của những thông báo này bằ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ởi cơ sở hạ tầng Trusty 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 ứng dụng Trusty sử dụng để hiển thị các điểm cuối dịch vụ dưới dạng một đường dẫn được đặt tên mà máy khách kết nối. Điều này cung cấp một 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 có, 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 đó giao tiếp xảy 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 đối xứng, hai chiều sẽ được thiết lập. Sử dụng đường dẫn song công này, máy khách và máy chủ có thể trao đổi các thông điệp tùy ý cho đến khi một trong hai bên quyết định hủy kết nối.
Chỉ các ứng dụng đáng tin cậy phía an toàn 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 an toàn (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ụ (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 máy khách).
API xử lý
Xử lý là các số nguyên không dấu đại diện cho các tài nguyên như cổng và kênh, tương tự như bộ mô tả tệp trong UNIX. Sau khi các xử lý được tạo, chúng được đặt vào một bảng xử lý 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 riêng tư với một xử lý bằng cách sử dụng phương thức set_cookie()
.
Các phương thức trong API xử lý
Xử lý 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 xử lý cho các ứng dụng khác trừ khi được chỉ định rõ ràng. Giá trị xử lý chỉ nên được diễn giải bằng cách so sánh với giá trị INVALID_IPC_HANDLE #define,
mà ứng dụng có thể sử dụng làm dấu hiệu cho thấy một xử lý không hợp lệ hoặc chưa được đặt.
set_cookie ()
Liên kết dữ liệu riêng tư do người gọi cung cấp với một xử lý được chỉ định.
long set_cookie(uint32_t handle, void *cookie)
Xử lý [in]: Bất kỳ handle
lý nào được trả về bởi một trong các lệnh gọi API
cookie
[in]: Con trỏ đến dữ liệu không gian người dùng tùy ý trong ứng dụng Trusty
[retval]: NO_ERROR
thành công, nếu không thì < 0
mã lỗi
Lời gọi này hữu ích để xử lý các sự kiện khi chúng xảy ra sau đó sau khi xử lý đã được tạo. Cơ chế xử lý sự kiện cung cấp trình xử lý và cookie của nó trở lại trình xử lý sự kiện.
Xử lý có thể được chờ đợi 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 xử lý nhất định trong một khoảng thời gian nhất định.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: Bất kỳ xử lý nào được trả về bởi một trong các lệnh gọi API
[out] event
: Một 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
[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 một sự kiện hợp lệ xảy ra trong một khoảng thời gian chờ được chỉ định; ERR_TIMED_OUT
nếu thời gian chờ được 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()
đ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 kế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
- phụ thuộc vào loại xử lý, như sau:
- Đối với các cổng, giá trị này cho biết rằng 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 kết nối không đồng bộ (xem
connect()
) đã được thiết lập
Các sự kiện sau chỉ phù hợp với các kênh:
-
IPC_HANDLE_POLL_HUP
- cho biết rằng một kênh đã bị đóng bởi một người 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 người gọi bị chặn gửi trước đó có thể cố gửi lại tin nhắn (xem mô tả củasend_msg()
để biết thêm chi tiết)
Một trình xử lý sự kiện nên được chuẩn bị để xử lý kế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ó các tin nhắn đang chờ xử lý và một kết nối bị đóng bởi một mạng 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à tình trạng cơ bản vẫn còn (ví dụ: tất cả các thông báo đang chờ xử lý được nhận và các yêu cầu kết nối đang chờ xử lý được xử lý). Ngoại lệ là trường hợp của sự kiện IPC_HANDLE_POLL_SEND_UNBLOCKED
, sự kiện này bị xóa khi đọc và ứng dụng chỉ có một cơ hội để xử lý.
Xử lý có thể bị phá hủy bằng cách gọi phương thức close()
.
gần()
Hủy tài nguyên được liên kết với xử lý được chỉ định và xóa nó khỏi bảng xử lý.
long close(uint32_t handle_id);
[in] handle_id
: Xử lý để phá hủy
[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á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 biểu diễn 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)
[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; cố gắng tạo một bản sao sẽ không thành công.
[in] num_recv_bufs
: Số bộ đệm tối đa mà một kênh trên cổng này có thể cấp phát trước để tạo điều kiện 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, vì vậy 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 yêu cầu 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ủ. Số lượng có thể là 1 trong trường hợp giao thức rất đồng bộ (gửi tin nhắn, nhận trả lời trước khi gửi một giao thức khác). Nhưng con số có thể nhiều hơn nếu khách hàng mong đợi 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 mở đầu và một tin nhắn khác dưới dạng lệnh thực). Các bộ đệm được phân bổ là trên mỗi kênh, vì vậy 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 thư tối đa mà bạn có thể trao đổi với đồng nghiệp
[in] flags
: Một tổ hợp 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 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 âm hoặc một lỗi cụ thể nếu âm
Sau đó, máy chủ sẽ thăm dò danh sách các chốt điều khiển 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 chỉ ra bởi bit IPC_HANDLE_POLL_READY
trong 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 đại diện bởi một tay cầm khác) sau đó có thể được thăm dò cho các thư đến .
Chấp nhận()
Chấp nhận 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ỏ đến cấu trúc uuud_t
được điền vào UUID của ứng dụng khách đang kết nối. Nó sẽ được đặt thành tất cả các số không nếu kết nối bắt nguồn từ thế giới không an toàn
[retval]: Xử lý kênh (nếu không phải là tiêu cực) mà trên đó máy chủ có thể trao đổi thông báo với máy khách (hoặc mã lỗi nếu không)
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
liên kết()
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);
[in] path
: Tên cổng được xuất bản bởi ứng dụng Trusty
[in] flags
: Chỉ định hành vi bổ sung, tùy chọn
[retval]: Xử lý một kênh mà thông điệp 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 sẽ 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 một cuộc gọi connect()
phải đợi 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ò ý kiến cho xử lý được trả về (bằng cách gọi wait()
cho sự kiện hoàn thành kết nối được chỉ ra bởi 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
Các 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 một bộ xử lý cho một kênh bằng cách thực hiện một cuộc gọi connect()
và một máy chủ nhận một bộ xử lý kênh từ một cuộc gọi accept()
, được mô tả ở trên.
Cấu trúc của một tin nhắn đáng tin cậy
Như được hiển thị trong phần sau, các thông báo đượ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 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 và ghi phân tán-tập hợp vào các khối này bằng cách sử dụng mảng iov
. Nội dung của bộ đệm có thể được mô tả bởi mảng iov
là hoàn toàn tùy ý.
Các phương thức trong API nhắn tin
send_msg ()
Gửi tin nhắn qua một kênh cụ thể.
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ỏ đến 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ắng gửi tin nhắn qua kênh và không có khoảng trống trong hàng đợi tin nhắn ngang hàng đích, kênh có thể chuyển sang trạng thái bị 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 các trường hợp phức tạp hơn) được chỉ ra 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 máy ngang hàng giải phóng một số không gian trong hàng đợi nhận của nó bằng cách truy xuất các thông báo xử lý và gỡ bỏ, được chỉ ra bởi 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 cụ thể.
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 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 tin nhắn được gán một ID duy nhất trên tập hợp các tin nhắn chưa trả 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 trả (đã mở) cùng một lúc cho một kênh cụ thể.
[retval]: NO_ERROR
về 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ừ độ lệch đượ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 từ đó
[in] msg_id
: ID của tin nhắn sẽ đọc
[in] offset
: Bù đắp vào thư mà từ đó bắt đầu đọc
[in] msg
: Con trỏ đến 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 dst
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 tại một độ lệch khác nhau (không nhất thiết phải tuần tự) nếu cần.
put_msg ()
Gỡ bỏ một tin nhắn với một ID được chỉ định.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: Xử lý kênh có tin nhắn đến
[in] msg_id
: ID của tin nhắn bị gỡ bỏ
[retval]: NO_ERROR
về 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 thư sau khi một thư đã được gỡ bỏ và bộ đệm mà nó chiếm đã được giải phóng.
API trình mô tả tệp
API trình mô tả tệp bao gồm các lệnh gọi read()
, write()
và ioctl()
. Tất cả các lệnh gọi này có thể hoạt động trên một tập hợp các bộ mô tả tệp được xác định trước (tĩnh) theo truyền thống được biểu diễn bằng các số nhỏ. Trong triển khai hiện tại, không gian bộ mô tả tệp tách biệt với không gian xử lý IPC. API trình mô tả tệp trong Trusty tương tự như API dựa trên trình 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à nổi tiếng):
- 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à cấm (vì các ứng dụng đáng tin cậy không được mong đợi có bảng điều khiển tương tác) nên việc đọc, ghi hoặc gọiioctl()
trênfd
0 sẽ trả về lỗiERR_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) đến UART và / hoặc nhật ký bộ nhớ có sẵn ở phía không an toàn, tùy thuộc vào nền tảng và cấu hình. Các bản ghi và thông báo gỡ lỗi không quan trọng phải ở đầu ra chuẩn. Các phương thức
read()
vàioctl()
là không hoạt động và sẽ trả về lỗiERR_NOT_SUPPORTED
. - 2 - sai số tiêu chuẩn. Dữ liệu được ghi vào lỗi chuẩn phải được chuyển đến UART hoặc nhật ký bộ nhớ có sẵn ở phía không an toàn, tùy thuộc vào nền tảng và cấu hình. Bạn chỉ nên viết các thông báo quan trọng cho lỗi chuẩn, vì luồng này rất có thể không có người theo dõi. Các phương thức
read()
vàioctl()
là không hoạt động và sẽ trả về lỗiERR_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 bộ mô tả tệp cần được thực hiện một cách thận trọng. Việc mở rộng bộ 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 trình 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 bộ đệm để lưu trữ dữ liệu
[in] count
: Số byte tối đa để đọc
[retval]: Số byte được trả về đã đọ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 cần ghi
[out] buf
: Con trỏ tới dữ liệu để ghi
[in] count
: Số byte tối đa để ghi
[retval]: Số byte được trả về đã được viết; một lỗi tiêu cực nếu không
ioctl ()
Gọi một 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 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, uint64_t *time);
[in] clock_id
: Phụ thuộc vào nền tảng; vượt qua số 0 cho mặc định
[in] flags
: Dành riêng, phải bằng 0
[in] time
: Con trỏ đến một giá trị int64_t
để lưu trữ thời gian hiện tại
[retval]: NO_ERROR
về thành công; một lỗi tiêu cực nếu không
nanosleep ()
Tạm dừng 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
: Đã đặt trước, 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
về thành công; một lỗi tiêu cực nếu không
Ví dụ về một máy chủ ứng dụng đáng tin cậy
Ứng dụng mẫu sau đây cho thấy việc sử dụng các API ở trên. Mẫu này 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ả các tin nhắn mà nó nhận được từ các 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 run_end_to_end_msg_test()
gửi không đồng bộ 10.000 thông báo tới 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 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 an toàn 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 nhân và không gian người dùng đang chạy ở phía không an toàn.
Môi trường thực thi ở phía không an toàn (nhân và không gian người dùng) khác hẳn 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 hạt nhân, API ứng dụng khách được cung cấp bởi trình điều khiển hạt nhân Trusty-ipc và đăng ký một nút thiết bị ký tự có thể được sử dụng bởi các quy trình không gian người dùng để giao tiếp với các dịch vụ chạy ở phía an toàn.
Không gian người dùng API ứng dụng khách IPC đáng tin cậy
Không gian người dùng Thư viện API máy khách Trusty IPC là một lớp mỏng trên đầu nút thiết bị fd
.
Một 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 đến một dịch vụ Trusty được chỉ định. Bên trong, lệnh gọi tipc_connect()
mở một nút thiết bị được chỉ định để lấy bộ mô tả tệp và gọi một 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 để giao tiếp với dịch vụ mà nó đã được tạo. Bộ mô tả tệp nên được đóng bằng cách gọi tipc_close()
khi kết nối không được yêu cầu nữa.
Bộ mô tả tệp thu được bởi lời gọi tipc_connect()
hoạt động như một nút thiết bị ký tự điển hình; trình mô tả tệp:
- 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 cuộc gọi
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 cuộc gọi
poll()
hoặcselect()
cuộc gọi) để biết tính khả dụng của các tin nhắn đến như một bộ mô tả tệp thông thường - Có thể đọ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 cuộc gọi ghi cho fd
được chỉ định. Tất cả dữ liệu được chuyển đến lệnh gọi write()
ở trên được trình điều khiển trusty-ipc chuyển thành 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 và được định tuyến đến đích thích hợp và được gửi tới 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 xử lý 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 lại cho phía không an toàn 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()
cuộc 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 một dịch vụ Trusty được chỉ định.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: Đường dẫn đến nút thiết bị IPC Trusty để 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ệ thành công, ngược lại là -1.
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
: Trình 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 khách Trusty IPC của nhân có sẵn cho các trình điều khiển nhân. Không gian người dùng API Trusty IPC được triển khai 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 người gọi tạo đối tượng struct tipc_chan
bằng cách sử dụng hàm tipc_create_channel()
và 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 đang chạy ở phía bảo mật. Kết nối với phía từ xa có thể được kết thúc 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 cuộc 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 thực hiện như sau:
- Nhận bộ đệm tin nhắn bằng lệnh gọi
tipc_chan_get_txbuf_timeout()
- Soạn một tin nhắn và
- Xếp hàng đợi 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), nơi 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ẽ trở lại vùng đệm miễn phí sau khi 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 nó không thể xếp hàng đợi bộ đệm đó hoặc nó không được yêu cầu nữa.
Người dùng API nhận thông báo từ phía từ 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 chuỗi công việc trusty-ipc rx
) cung cấp một con trỏ đến bộ đệm rx
có chứa thông báo đến được xử lý.
Dự kiến rằng việc triển khai cuộc gọi lại handle_msg()
sẽ trả về một con trỏ đến một cấu trúc hợp lệ struct tipc_msg_buf
. 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ời gọi tipc_chan_get_rxbuf()
nếu bộ đệm đến được xếp hàng đợi để xử lý thêm. 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ời gọi tipc_chan_put_rxbuf()
khi nó không còn cần thiết nữa.
Các phương thức trong API ứng dụng khách IPC đáng tin cậy của nhân
tipc_create_channel ()
Tạo và định cấu hình một 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ỏ đến ipc đáng tin cậy mà kênh thiết bị được tạo
[in] ops
: Trỏ tới 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 các lệnh gọi lại tipc_chan_ops
[retval]: 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 đang diễn ra.
Sự kiện 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 chuyể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 điều khiển 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 mặt điều khiể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 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ụ thể:
- [in]
cb_arg
: Con trỏ tới dữ liệu được chuyển đến lệnh gọitipc_create_channel()
- [in]
mb
: Con trỏ tớistruct tipc_msg_buf
mô tả một tin nhắn đến - [retval]: Việc triển khai cuộc gọi lại dự kiến sẽ trả về một con trỏ đến
struct tipc_msg_buf
có thể là con trỏ giống như một 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à một bộ đệm mới được lấy bởitipc_chan_get_rxbuf()
call)
tipc_chan_connect ()
Khởi tạo 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ỏ đến một kênh được trả về bởi lệnh gọi tipc_create_chan()
port
[in]: 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 tiêu cực
Người gọi được thông báo khi kết nối được thiết lập bằng cách nhận một cuộc gọi lại handle_event
.
tipc_chan_shutdown ()
Chấm dứt kết nối với dịch vụ Trusty IPC đã 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ỏ đến một kênh được trả về bởi lệnh gọi tipc_create_chan()
tipc_chan_destroy ()
Hủy một kênh IPC đáng tin cậy được chỉ định.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: Con trỏ đến một kênh được trả về bởi lệnh gọi tipc_create_chan()
tipc_chan_get_txbuf_timeout ()
Nhận 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 khả dụng ngay lập tức, người gọi có thể bị chặn trong 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);
[in] chan
: Trỏ đến kênh để xếp hàng một 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)
do lỗi
tipc_chan_queue_msg ()
Xếp hàng đợi một 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
: Trỏ đến kênh để xếp hàng đợi tin nhắn
[in] mb:
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 khi thành công, nếu không thì là lỗi tiêu cực
tipc_chan_put_txbuf ()
Giải phóng bộ đệm tin nhắn Tx
được 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ỏ đến kênh mà bộ đệm tin nhắn này thuộc về
[in] mb
: Con trỏ tới bộ đệm thư để phát hành
[retval]: Không có
tipc_chan_get_rxbuf ()
Nhận bộ đệm tin nhắn mới có thể được sử dụng để nhận tin nhắn qua kênh đã chỉ định.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: Con trỏ đến một kênh mà bộ đệm tin nhắn này thuộc về
[retval]: Bộ đệm thông báo hợp lệ khi thành công, ERR_PTR(err)
do lỗi
tipc_chan_put_rxbuf ()
Giải phóng bộ đệm tin nhắn cụ thể đã thu được trước đó bằng cuộc gọi tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: Con trỏ đến một kênh mà bộ đệm tin nhắn này thuộc về
[in] mb
: Con trỏ tới bộ đệm tin nhắn để phát hành
[retval]: Không có