Trusty cung cấp các API để phát triển hai loại ứng dụng và dịch vụ:
- Các ứng dụng và dịch vụ đáng tin cậy chạy trên bộ xử lý TEE
- Ứng dụng thông thường và không đáng tin cậy chạy trên bộ xử lý chính và sử dụng các dịch vụ do ứng dụng đáng tin cậy cung cấp
API Trusty thường mô tả hệ thống giao tiếp giữa các quá trình (IPC) Trusty, bao gồm cả giao tiếp với thế giới không an toàn. Phần mềm chạy trên bộ xử lý chính có thể sử dụng API đáng tin cậy để kết nối với các ứng dụng và dịch vụ đáng tin cậy, đồng thời trao đổi thông báo tuỳ ý với các ứng dụng và dịch vụ đó giống như một dịch vụ mạng qua IP. Ứng dụng có thể xác định định dạng dữ liệu và ngữ nghĩa của các thông báo này bằng cách sử dụng giao thức cấp ứng dụng. Việc phân phối tin nhắn đáng tin cậy được đảm bảo bằng cơ sở hạ tầng Trusty cơ bản (ở dạng trình điều khiển chạy trên bộ xử lý chính) và hoạt động giao tiếp hoàn toàn không đồng bộ.
Cổng và kênh
Các cổng được ứng dụng Trusty sử dụng để hiển thị các điểm cuối dịch vụ ở dạng đường dẫn được đặt tên mà ứng dụng khách kết nối. Thao tác này sẽ cung cấp một mã dịch vụ đơn giản, dựa trên chuỗi để ứng dụng khách sử dụng. Quy ước đặt tên là đặt tên theo kiểu DNS ngược, ví dụ: com.google.servicename
.
Khi kết nối với một cổng, ứng dụng sẽ nhận được một kênh để tương tác với một dịch vụ. Dịch vụ phải chấp nhận kết nối đến và khi chấp nhận, dịch vụ cũng sẽ nhận được một kênh. Về cơ bản, cổng được dùng để tra cứu dịch vụ, sau đó quá trình giao tiếp sẽ diễn ra qua một cặp kênh đã kết nối (tức là các thực thể kết nối trên một cổng). Khi một ứng dụng kết nối với một cổng, một kết nối hai chiều, đối xứng sẽ được thiết lập. Khi sử dụng đường dẫn toàn bộ song công này, ứng dụng và máy chủ có thể trao đổi thông báo tuỳ ý cho đến khi một bên quyết định huỷ kết nối.
Chỉ những ứng dụng đáng tin cậy ở phía bảo mật hoặc mô-đun hạt nhân Trusty mới có thể tạo các cổng. Các ứng dụng chạy ở phía không an toàn (trong thế giới thực) chỉ có thể kết nối với các dịch vụ do phía an toàn phát hành.
Tuỳ thuộc vào yêu cầu, ứng dụng đáng tin cậy có thể vừa là ứng dụng khách vừa là máy chủ. Một ứng dụng đáng tin cậy phát hành một dịch vụ (dưới dạng máy chủ) có thể cần kết nối với các dịch vụ khác (dưới dạng ứng dụng khách).
Xử lý API
Tay điều khiển là các số nguyên chưa ký đại diện cho các tài nguyên như cổng và kênh, tương tự như chỉ số mô tả tệp trong UNIX. Sau khi được tạo, các handle sẽ được đặt vào bảng handle dành riêng cho ứng dụng và có thể được tham chiếu sau.
Phương thức gọi có thể liên kết dữ liệu riêng tư với một tên người dùng bằng cách sử dụng phương thức set_cookie()
.
Các phương thức trong API Handle
Tên người dùng chỉ hợp lệ trong ngữ cảnh của một ứng dụng. Ứng dụng không được truyền giá trị của tên người dùng sang các ứng dụng khác trừ phi được chỉ định rõ ràng. Bạn chỉ nên diễn giải giá trị tay cầm bằng cách so sánh giá trị đó với INVALID_IPC_HANDLE #define,
mà ứng dụng có thể sử dụng làm chỉ báo cho biết tay cầm không hợp lệ hoặc chưa được đặt.
set_cookie()
Liên kết dữ liệu riêng tư do phương thức gọi cung cấp với một tên người dùng đã chỉ định.
long set_cookie(uint32_t handle, void *cookie)
[trong] handle
: Mọi tay cầm do một trong các lệnh gọi API trả về
[trong] cookie
: Con trỏ đến dữ liệu không gian người dùng tuỳ ý trong ứng dụng Trusty
[retval]: NO_ERROR
nếu thành công, mã lỗi < 0
nếu không
Lệnh gọi này hữu ích trong việc xử lý các sự kiện khi chúng xảy ra sau khi lệnh gọi xử lý được tạo. Cơ chế xử lý sự kiện cung cấp handle và cookie của handle đó trở lại trình xử lý sự kiện.
Bạn có thể chờ các sự kiện bằng cách sử dụng lệnh gọi wait()
.
wait()
Chờ một sự kiện xảy ra trên một tay điều khiển nhất định trong một khoảng thời gian cụ thể.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[trong] handle_id
: Mọi tay cầm do một trong các lệnh gọi API trả về
[out] event
: Con trỏ đến cấu trúc đại diện cho một sự kiện đã xảy ra trên tay cầm này
[trong] timeout_msecs
: Giá trị thời gian chờ tính bằng mili giây; giá trị -1 là thời gian chờ vô hạn
[retval]: NO_ERROR
nếu một sự kiện hợp lệ xảy ra trong khoảng thời gian chờ đã chỉ định; ERR_TIMED_OUT
nếu khoảng thời gian chờ đã chỉ định đã trôi qua nhưng không có sự kiện nào xảy ra; < 0
đối với các lỗi khác
Khi thành công (retval == NO_ERROR
), lệnh gọi wait()
sẽ điền thông tin về sự kiện đã xảy ra vào một cấu trúc uevent_t
đã chỉ định.
typedef struct uevent { uint32_t handle; /* handle this event is related to */ uint32_t event; /* combination of IPC_HANDLE_POLL_XXX flags */ void *cookie; /* cookie associated with this handle */ } uevent_t;
Trường event
chứa tổ hợp các giá trị sau:
enum { IPC_HANDLE_POLL_NONE = 0x0, IPC_HANDLE_POLL_READY = 0x1, IPC_HANDLE_POLL_ERROR = 0x2, IPC_HANDLE_POLL_HUP = 0x4, IPC_HANDLE_POLL_MSG = 0x8, IPC_HANDLE_POLL_SEND_UNBLOCKED = 0x10, … more values[TBD] };
IPC_HANDLE_POLL_NONE
– không có sự kiện nào thực sự đang chờ xử lý, người gọi nên bắt đầu lại quá trình chờ
IPC_HANDLE_POLL_ERROR
– đã xảy ra lỗi nội bộ không xác định
IPC_HANDLE_POLL_READY
– tuỳ thuộc vào loại tên người dùng, như sau:
- Đối với cổng, giá trị này cho biết có một kết nối đang chờ xử lý
- Đối với các kênh, giá trị này cho biết rằng một kết nối không đồng bộ (xem
connect()
) đã được thiết lập
Các sự kiện sau đây chỉ liên quan đến kênh:
IPC_HANDLE_POLL_HUP
– cho biết một kênh đã bị một đối tác đóngIPC_HANDLE_POLL_MSG
– cho biết có một tin nhắn đang chờ xử lý cho kênh nàyIPC_HANDLE_POLL_SEND_UNBLOCKED
– cho biết rằng một phương thức gọi bị chặn gửi trước đó có thể cố gắng gửi lại một thông báo (xem nội dung mô tả vềsend_msg()
để biết thông tin chi tiết)
Bạn nên chuẩn bị trình xử lý sự kiện để xử lý tổ hợp các sự kiện được chỉ định, vì nhiều bit có thể được đặt cùng một lúc. Ví dụ: đối với một kênh, có thể có thông báo đang chờ xử lý và một kết nối do một máy ngang hàng đóng cùng lúc.
Hầu hết các sự kiện đều cố định. Các trạng thái này vẫn tồn tại miễn là điều kiện cơ bản vẫn tồn tại (ví dụ: tất cả thông báo đang chờ xử lý đều được nhận và các yêu cầu kết nối đang chờ xử lý đều được xử lý). Trường hợp ngoại lệ là trường hợp của sự kiện IPC_HANDLE_POLL_SEND_UNBLOCKED
. Sự kiện này sẽ bị xoá sau khi đọc và ứng dụng chỉ có một cơ hội để xử lý sự kiện đó.
Bạn có thể huỷ các tay điều khiển bằng cách gọi phương thức close()
.
close()
Huỷ bỏ tài nguyên được liên kết với handle đã chỉ định và xoá tài nguyên đó khỏi bảng handle.
long close(uint32_t handle_id);
[trong] handle_id
: Xử lý để huỷ
[retval]: 0 nếu thành công; lỗi âm nếu không
API máy chủ
Máy chủ bắt đầu bằng cách tạo một hoặc nhiều cổng được đặt tên đại diện cho các điểm cuối dịch vụ của máy chủ. Mỗi cổng được biểu thị bằng một tay cầm.
Các phương thức trong API máy chủ
port_create()
Tạo một cổng dịch vụ được đặt tên.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[trong] path
: Tên chuỗi của cổng (như mô tả ở trên). Tên này phải là duy nhất trên hệ thống; các nỗ lực tạo tên trùng lặp sẽ không thành công.
[trong] num_recv_bufs
: Số lượng bộ đệm tối đa mà một kênh trên cổng này có thể phân bổ trước để tạo điều kiện trao đổi dữ liệu với ứng dụng. Các vùng đệm được tính riêng cho dữ liệu đi theo cả hai hướng, vì vậy, việc chỉ định 1 ở đây có nghĩa là 1 vùng đệm gửi và 1 vùng đệm nhận được phân bổ trước. Nhìn chung, số lượng vùng đệm cần thiết phụ thuộc vào thoả thuận giao thức cấp cao hơn giữa ứng dụng và máy chủ. Số lượng có thể chỉ là 1 trong trường hợp giao thức rất đồng bộ (gửi thông báo, nhận thư trả lời trước khi gửi thông báo khác). Nhưng số lượng có thể nhiều hơn nếu ứng dụng dự kiến gửi nhiều thông báo trước khi có thể trả lời (ví dụ: một thông báo là lời mở đầu và một thông báo khác là lệnh thực tế). Các nhóm bộ đệm được phân bổ là theo kênh, vì vậy, hai kết nối (kênh) riêng biệt sẽ có các nhóm bộ đệm riêng biệt.
[trong] recv_buf_size
: Kích thước tối đa của từng bộ đệm riêng lẻ trong bộ đệm ở trên. Giá trị này phụ thuộc vào giao thức và giới hạn hiệu quả kích thước tối đa của thông báo mà bạn có thể trao đổi với máy ngang hàng
[trong] flags
: Một tổ hợp cờ chỉ định hành vi cổng bổ sung
Giá trị này phải là tổ hợp của các giá trị sau:
IPC_PORT_ALLOW_TA_CONNECT
– cho phép kết nối từ các ứng dụng bảo mật khác
IPC_PORT_ALLOW_NS_CONNECT
– cho phép kết nối từ môi trường không an toàn
[retval]: Giá trị xử lý của cổng được tạo nếu không âm hoặc một lỗi cụ thể nếu âm
Sau đó, máy chủ sẽ thăm dò ý kiến danh sách tay cầm cổng cho các kết nối đến bằng lệnh gọi wait()
. Khi nhận được yêu cầu kết nối được biểu thị bằng bit IPC_HANDLE_POLL_READY
được đặt trong trường event
của cấu trúc uevent_t
, máy chủ sẽ gọi accept()
để hoàn tất việc thiết lập kết nối và tạo một kênh (được biểu thị bằng một tay điều khiển khác) sau đó có thể được thăm dò ý kiến về các thông báo đến.
accept()
Chấp nhận kết nối đến và nhận tên người dùng cho một kênh.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[trong] handle_id
: Tên xử lý đại diện cho cổng mà ứng dụng đã kết nối
[out] peer_uuid
: Con trỏ đến cấu trúc uuid_t
sẽ được điền bằng UUID của ứng dụng khách đang kết nối. Giá trị này được đặt thành tất cả các số 0 nếu kết nối bắt nguồn từ thế giới không an toàn
[retval]: Tên xử lý cho một kênh (nếu không âm) mà máy chủ có thể trao đổi thông báo với ứng dụng (hoặc mã lỗi nếu không)
API ứng dụng
Phần này chứa các phương thức trong API ứng dụng.
Các phương thức trong API ứng dụng
connect()
Khởi tạo kết nối với một cổng được chỉ định theo tên.
long connect(const char *path, uint flags);
[trong] path
: Tên của một cổng do ứng dụng Trusty phát hành
[trong] flags
: Chỉ định hành vi bổ sung, không bắt buộc
[retval]: Tên xử lý của một kênh mà qua đó có thể trao đổi thông báo với máy chủ; lỗi nếu giá trị âm
Nếu không chỉ định flags
(tham số flags
được đặt thành 0), thì việc gọi connect()
sẽ bắt đầu một kết nối đồng bộ với một cổng được chỉ định. Kết nối này sẽ trả về lỗi ngay lập tức nếu cổng không tồn tại và tạo một khối cho đến khi máy chủ chấp nhận kết nối.
Bạn có thể thay đổi hành vi này bằng cách chỉ định tổ hợp hai giá trị, được mô tả bên dưới:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
– buộc lệnh gọi connect()
phải chờ nếu cổng được chỉ định không tồn tại ngay lập tức tại thời điểm thực thi, thay vì ngay lập tức báo lỗi.
IPC_CONNECT_ASYNC
– nếu được đặt, sẽ bắt đầu một kết nối không đồng bộ. Ứng dụng phải thăm dò ý kiến về tay điều khiển được trả về bằng cách gọi wait()
cho sự kiện hoàn tất kết nối được chỉ báo bằng bit IPC_HANDLE_POLL_READY
được đặt trong trường sự kiện của cấu trúc uevent_t
trước khi bắt đầu hoạt động bình thường.
Messaging API
Các lệnh gọi API Messaging cho phép gửi và đọc tin nhắn qua một kết nối (kênh) đã thiết lập trước đó. Các lệnh gọi API Messaging giống nhau đối với máy chủ và ứng dụng.
Ứng dụng nhận được tên người dùng cho một kênh bằng cách đưa ra lệnh gọi connect()
và máy chủ nhận được tên người dùng của kênh từ lệnh gọi accept()
như mô tả ở trên.
Cấu trúc của thông báo Tin cậy
Như minh hoạ trong phần sau, các thông báo do Trusty API trao đổi có cấu trúc tối thiểu, để máy chủ và ứng dụng đồng ý về ngữ nghĩa của nội dung thực tế:
/* * IPC message */ typedef struct iovec { void *base; size_t len; } iovec_t; typedef struct ipc_msg { uint num_iov; /* number of iovs in this message */ iovec_t *iov; /* pointer to iov array */ uint num_handles; /* reserved, currently not supported */ handle_t *handles; /* reserved, currently not supported */ } ipc_msg_t;
Một thông báo có thể bao gồm một hoặc nhiều vùng đệm không liền kề được biểu thị bằng một mảng cấu trúc iovec_t
. Trusty thực hiện các thao tác đọc và ghi thu thập dữ liệu phân tán vào các khối này bằng cách sử dụng mảng iov
. Nội dung của vùng đệm có thể được mô tả bằng mảng iov
là hoàn toàn tuỳ ý.
Các phương thức trong Messaging API
send_msg()
Gửi thông báo qua một kênh đã chỉ định.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[trong] handle
: Tên người dùng của kênh để gửi thông báo
[trong] msg
: Con trỏ đến ipc_msg_t structure
mô tả thông báo
[retval]: Tổng số byte được gửi khi thành công; nếu không thì sẽ là lỗi âm
Nếu ứng dụng (hoặc máy chủ) đang cố gửi tin nhắn qua kênh và không có không gian trong hàng đợi tin nhắn ngang hàng đích, thì kênh có thể chuyển sang trạng thái bị chặn gửi (điều này không bao giờ xảy ra với giao thức yêu cầu/trả lời đồng bộ đơn giản nhưng có thể xảy ra trong các trường hợp phức tạp hơn) được biểu thị bằng cách trả về mã lỗi ERR_NOT_ENOUGH_BUFFER
.
Trong trường hợp như vậy, phương thức gọi phải đợi cho đến khi máy ngang cấp giải phóng một số không gian trong hàng đợi nhận bằng cách truy xuất các thông báo xử lý và rút lui, được biểu thị bằng bit IPC_HANDLE_POLL_SEND_UNBLOCKED
được đặt trong trường event
của cấu trúc uevent_t
do lệnh gọi wait()
trả về.
get_msg()
Lấy siêu thông tin về thư tiếp theo trong hàng đợi thư đến
của một kênh cụ thể.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[trong] handle
: Tên người dùng của kênh mà bạn phải truy xuất tin nhắn mới
[out] msg_info
: Cấu trúc thông tin thông báo được mô tả như sau:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Mỗi thông báo được chỉ định một mã nhận dạng duy nhất trong tập hợp thông báo chưa xử lý và tổng chiều dài của mỗi thông báo được điền sẵn. Nếu được giao thức định cấu hình và cho phép, có thể có nhiều thông báo chưa xử lý (đã mở) cùng lúc cho một kênh cụ thể.
[retval]: NO_ERROR
nếu thành công; lỗi âm nếu không
read_msg()
Đọc nội dung của thông báo có mã nhận dạng được chỉ định bắt đầu từ độ dời được chỉ định.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[trong] handle
: Tên người dùng của kênh để đọc thư
[trong] msg_id
: Mã nhận dạng của thư cần đọc
[trong] offset
: Độ dời vào thư để bắt đầu đọc
[out] msg
: Con trỏ đến cấu trúc ipc_msg_t
mô tả một nhóm bộ đệm để lưu trữ dữ liệu thông báo đến
[retval]: Tổng số byte được lưu trữ trong vùng đệm msg
khi thành công; nếu không thì sẽ là lỗi âm
Phương thức read_msg
có thể được gọi nhiều lần bắt đầu từ một độ dời khác (không nhất thiết phải tuần tự) nếu cần.
put_msg()
Rút lại một thông báo có mã nhận dạng được chỉ định.
long put_msg(uint32_t handle, uint32_t msg_id);
[trong] handle
: Tên người dùng của kênh đã nhận được thông báo
[trong] msg_id
: Mã của thông báo sắp ngừng hoạt động
[retval]: NO_ERROR
nếu thành công; lỗi âm nếu không
Không thể truy cập vào nội dung của thư sau khi thư đã ngừng hoạt động và vùng đệm mà thư đó chiếm đã được giải phóng.
API chỉ số mô tả tệp
API Chỉ số mô tả tệp bao gồm các lệnh gọi read()
, write()
và ioctl()
. Tất cả các lệnh gọi này đều có thể hoạt động trên một tập hợp các chỉ số mô tả tệp (static) được xác định trước (thường được biểu thị bằng các số nhỏ). Trong quá trình triển khai hiện tại, không gian chỉ số mô tả tệp tách biệt với không gian tay cầm IPC. API chỉ số mô tả tệp trong Trusty tương tự như API dựa trên chỉ số mô tả tệp truyền thống.
Theo mặc định, có 3 chỉ số mô tả tệp (tiêu chuẩn và phổ biến) được xác định trước:
- 0 – đầu vào chuẩn. Việc triển khai mặc định của dữ liệu đầu vào chuẩn
fd
là không hoạt động (vì các ứng dụng đáng tin cậy không được dự kiến sẽ có bảng điều khiển tương tác), vì vậy, việc đọc, ghi hoặc gọiioctl()
trênfd
0 sẽ trả về lỗiERR_NOT_SUPPORTED
. - 1 – đầu ra chuẩn. Dữ liệu được ghi vào đầu ra chuẩn có thể được định tuyến (tuỳ thuộc vào cấp gỡ lỗi LK) đến UART và/hoặc nhật ký bộ nhớ có sẵn ở phía không an toàn, tuỳ thuộc vào nền tảng và cấu hình. Nhật ký gỡ lỗi và thông báo không quan trọng phải nằm trong đầu ra tiêu chuẩn. Các phương thức
read()
vàioctl()
không có tác dụng và sẽ trả về lỗiERR_NOT_SUPPORTED
. - 2 – sai số chuẩn. Dữ liệu được ghi vào lỗi chuẩn phải được định tuyến đến UART hoặc nhật ký bộ nhớ có sẵn ở phía không bảo mật, tuỳ thuộc vào nền tảng và cấu hình. Bạn chỉ nên ghi các thông báo quan trọng vào lỗi tiêu chuẩn, vì luồng này rất có thể sẽ không bị điều tiết. Các phương thức
read()
vàioctl()
không có tác dụng và sẽ trả về lỗiERR_NOT_SUPPORTED
.
Mặc dù có thể mở rộng tập hợp chỉ số mô tả tệp này để triển khai thêm fds
(để triển khai các tiện ích dành riêng cho nền tảng), nhưng bạn cần thận trọng khi mở rộng chỉ số mô tả tệp. Việc mở rộng chỉ số mô tả tệp dễ gây ra xung đột và thường không được khuyến khích.
Các phương thức trong API chỉ số mô tả tệp
read()
Cố gắng đọc tối đa count
byte dữ liệu từ một chỉ số mô tả tệp được chỉ định.
long read(uint32_t fd, void *buf, uint32_t count);
[trong] fd
: Chỉ số mô tả tệp để đọc
[out] buf
: Con trỏ đến vùng đệm để lưu trữ dữ liệu
[trong] count
: Số byte tối đa cần đọc
[retval]: Số byte đã đọc được trả về; nếu không thì sẽ trả về lỗi âm
write()
Ghi tối đa count
byte dữ liệu vào chỉ số mô tả tệp đã chỉ định.
long write(uint32_t fd, void *buf, uint32_t count);
[trong] fd
: Chỉ số mô tả tệp cần ghi
[out] buf
: Con trỏ đến dữ liệu cần ghi
[trong] count
: Số byte tối đa cần ghi
[retval]: Số byte được ghi trả về; nếu không, lỗi âm
ioctl()
Gọi một lệnh ioctl
đã chỉ định cho một chỉ số mô tả tệp nhất định.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[trong] fd
: Chỉ số mô tả tệp để gọi ioctl()
[trong] cmd
: Lệnh ioctl
[vào/ra] args
: Con trỏ đến đối số ioctl()
API khác
Các phương thức trong API Khác
gettime()
Trả về thời gian hệ thống hiện tại (tính bằng nano giây).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[trong] clock_id
: Tuỳ thuộc vào nền tảng; truyền giá trị 0 cho giá trị mặc định
[trong] flags
: Dành riêng, phải bằng 0
[out] time
: Con trỏ đến giá trị int64_t
để lưu trữ thời gian hiện tại
[retval]: NO_ERROR
nếu thành công; lỗi âm nếu không
nanosleep()
Tạm ngưng thực thi ứng dụng gọi trong một khoảng thời gian cụ thể và tiếp tục thực thi sau khoảng thời gian đó.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[trong] clock_id
: Dành riêng, phải bằng 0
[trong] flags
: Dành riêng, phải bằng 0
[trong] sleep_time
: Thời gian ngủ tính bằng nano giây
[retval]: NO_ERROR
nếu thành công; lỗi âm nếu không
Ví dụ về máy chủ ứng dụng đáng tin cậy
Ứng dụng mẫu sau đây cho thấy cách sử dụng các API ở trên. Mẫu này tạo một dịch vụ "echo" (hồi âm) xử lý nhiều kết nối đến và phản ánh lại cho phương thức gọi tất cả thông báo mà phương thức đó nhận được từ các ứng dụng bắt nguồn từ phía an toàn hoặc không an toàn.
#include <uapi/err.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <trusty_ipc.h> #define LOG_TAG "echo_srv" #define TLOGE(fmt, ...) \ fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ##__VA_ARGS__) # define MAX_ECHO_MSG_SIZE 64 static const char * srv_name = "com.android.echo.srv.echo"; static uint8_t msg_buf[MAX_ECHO_MSG_SIZE]; /* * Message handler */ static int handle_msg(handle_t chan) { int rc; struct iovec iov; ipc_msg_t msg; ipc_msg_info_t msg_inf; iov.iov_base = msg_buf; iov.iov_len = sizeof(msg_buf); msg.num_iov = 1; msg.iov = &iov; msg.num_handles = 0; msg.handles = NULL; /* get message info */ rc = get_msg(chan, &msg_inf); if (rc == ERR_NO_MSG) return NO_ERROR; /* no new messages */ if (rc != NO_ERROR) { TLOGE("failed (%d) to get_msg for chan (%d)\n", rc, chan); return rc; } /* read msg content */ rc = read_msg(chan, msg_inf.id, 0, &msg); if (rc < 0) { TLOGE("failed (%d) to read_msg for chan (%d)\n", rc, chan); return rc; } /* update number of bytes received */ iov.iov_len = (size_t) rc; /* send message back to the caller */ rc = send_msg(chan, &msg); if (rc < 0) { TLOGE("failed (%d) to send_msg for chan (%d)\n", rc, chan); return rc; } /* retire message */ rc = put_msg(chan, msg_inf.id); if (rc != NO_ERROR) { TLOGE("failed (%d) to put_msg for chan (%d)\n", rc, chan); return rc; } return NO_ERROR; } /* * Channel event handler */ static void handle_channel_event(const uevent_t * ev) { int rc; if (ev->event & IPC_HANDLE_POLL_MSG) { rc = handle_msg(ev->handle); if (rc != NO_ERROR) { /* report an error and close channel */ TLOGE("failed (%d) to handle event on channel %d\n", rc, ev->handle); close(ev->handle); } return; } if (ev->event & IPC_HANDLE_POLL_HUP) { /* closed by peer. */ close(ev->handle); return; } } /* * Port event handler */ static void handle_port_event(const uevent_t * ev) { uuid_t peer_uuid; if ((ev->event & IPC_HANDLE_POLL_ERROR) || (ev->event & IPC_HANDLE_POLL_HUP) || (ev->event & IPC_HANDLE_POLL_MSG) || (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) { /* should never happen with port handles */ TLOGE("error event (0x%x) for port (%d)\n", ev->event, ev->handle); abort(); } if (ev->event & IPC_HANDLE_POLL_READY) { /* incoming connection: accept it */ int rc = accept(ev->handle, &peer_uuid); if (rc < 0) { TLOGE("failed (%d) to accept on port %d\n", rc, ev->handle); return; } handle_t chan = rc; while (true){ struct uevent cev; rc = wait(chan, &cev, INFINITE_TIME); if (rc < 0) { TLOGE("wait returned (%d)\n", rc); abort(); } handle_channel_event(&cev); if (cev.event & IPC_HANDLE_POLL_HUP) { return; } } } } /* * Main app entry point */ int main(void) { int rc; handle_t port; /* Initialize service */ rc = port_create(srv_name, 1, MAX_ECHO_MSG_SIZE, IPC_PORT_ALLOW_NS_CONNECT | IPC_PORT_ALLOW_TA_CONNECT); if (rc < 0) { TLOGE("Failed (%d) to create port %s\n", rc, srv_name); abort(); } port = (handle_t) rc; /* enter main event loop */ while (true) { uevent_t ev; ev.handle = INVALID_IPC_HANDLE; ev.event = 0; ev.cookie = NULL; /* wait forever */ rc = wait(port, &ev, INFINITE_TIME); if (rc == NO_ERROR) { /* got an event */ handle_port_event(&ev); } else { TLOGE("wait returned (%d)\n", rc); abort(); } } return 0; }
Phương thức run_end_to_end_msg_test()
gửi 10.000 thông báo không đồng bộ đến dịch vụ "echo" và xử lý các phản hồi.
static int run_echo_test(void) { int rc; handle_t chan; uevent_t uevt; uint8_t tx_buf[64]; uint8_t rx_buf[64]; ipc_msg_info_t inf; ipc_msg_t tx_msg; iovec_t tx_iov; ipc_msg_t rx_msg; iovec_t rx_iov; /* prepare tx message buffer */ tx_iov.base = tx_buf; tx_iov.len = sizeof(tx_buf); tx_msg.num_iov = 1; tx_msg.iov = &tx_iov; tx_msg.num_handles = 0; tx_msg.handles = NULL; memset (tx_buf, 0x55, sizeof(tx_buf)); /* prepare rx message buffer */ rx_iov.base = rx_buf; rx_iov.len = sizeof(rx_buf); rx_msg.num_iov = 1; rx_msg.iov = &rx_iov; rx_msg.num_handles = 0; rx_msg.handles = NULL; /* open connection to echo service */ rc = sync_connect(srv_name, 1000); if(rc < 0) return rc; /* got channel */ chan = (handle_t)rc; /* send/receive 10000 messages asynchronously. */ uint tx_cnt = 10000; uint rx_cnt = 10000; while (tx_cnt || rx_cnt) { /* send messages until all buffers are full */ while (tx_cnt) { rc = send_msg(chan, &tx_msg); if (rc == ERR_NOT_ENOUGH_BUFFER) break; /* no more space */ if (rc != 64) { if (rc > 0) { /* incomplete send */ rc = ERR_NOT_VALID; } goto abort_test; } tx_cnt--; } /* wait for reply msg or room */ rc = wait(chan, &uevt, 1000); if (rc != NO_ERROR) goto abort_test; /* drain all messages */ while (rx_cnt) { /* get a reply */ rc = get_msg(chan, &inf); if (rc == ERR_NO_MSG) break; /* no more messages */ if (rc != NO_ERROR) goto abort_test; /* read reply data */ rc = read_msg(chan, inf.id, 0, &rx_msg); if (rc != 64) { /* unexpected reply length */ rc = ERR_NOT_VALID; goto abort_test; } /* discard reply */ rc = put_msg(chan, inf.id); if (rc != NO_ERROR) goto abort_test; rx_cnt--; } } abort_test: close(chan); return rc; }
Các ứng dụng và API không an toàn
Một nhóm dịch vụ Trusty, được phát hành từ phía bảo mật và được đánh dấu bằng thuộc tính IPC_PORT_ALLOW_NS_CONNECT
, có thể truy cập vào các chương trình không gian hạt nhân và không gian người dùng chạy ở phía không bảo mật.
Môi trường thực thi ở phía không bảo mật (hạt nhân và không gian người dùng) khác biệt đáng kể so với môi trường thực thi ở phía bảo mật. Do đó, thay vì một thư viện duy nhất cho cả hai môi trường, sẽ có hai bộ API khác nhau. Trong nhân, API ứng dụng do trình điều khiển nhân trusty-ipc cung cấp và đăng ký một nút thiết bị ký tự mà các quy trình không gian người dùng có thể sử dụng để giao tiếp với các dịch vụ chạy ở phía bảo mật.
API ứng dụng Trusty IPC không gian người dùng
Thư viện API ứng dụng Trusty IPC là một lớp mỏng trên nút thiết bị fd
.
Chương trình không gian người dùng bắt đầu một phiên giao tiếp bằng cách gọi tipc_connect()
, khởi tạo kết nối với một dịch vụ Trusty được chỉ định. Trong nội bộ, lệnh gọi tipc_connect()
sẽ mở một nút thiết bị được chỉ định để lấy chỉ số mô tả tệp và gọi lệnh gọi TIPC_IOC_CONNECT ioctl()
với tham số argp
trỏ đến một chuỗi chứa tên dịch vụ cần kết nối.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
Chỉ có thể dùng chỉ số mô tả tệp thu được để giao tiếp với dịch vụ mà chỉ số mô tả tệp đó được tạo ra. Bạn nên đóng chỉ số mô tả tệp bằng cách gọi tipc_close()
khi không cần kết nối nữa.
Chỉ số mô tả tệp thu được bằng lệnh gọi tipc_connect()
hoạt động như một nút thiết bị ký tự thông thường; chỉ số mô tả tệp:
- Có thể chuyển sang chế độ không chặn nếu cần
- Có thể ghi vào bằng lệnh gọi
write()
tiêu chuẩn để gửi thông báo đến bên kia - Có thể được thăm dò ý kiến (sử dụng lệnh gọi
poll()
hoặc lệnh gọiselect()
) để biết trạng thái có sẵn của thông báo đến dưới dạng chỉ số mô tả tệp thông thường - Có thể đọc để truy xuất tin nhắn đến
Phương thức gọi gửi một thông báo đến dịch vụ Trusty bằng cách thực thi lệnh gọi ghi cho fd
đã chỉ định. Tất cả dữ liệu được truyền đến lệnh gọi write()
ở trên sẽ được trình điều khiển trusty-ipc chuyển đổi thành một thông báo. Thông báo được gửi đến phía bảo mật, nơi dữ liệu được xử lý bởi hệ thống con IPC trong hạt nhân Trusty, được định tuyến đến đích thích hợp và được gửi đến vòng lặp sự kiện của ứng dụng dưới dạng sự kiện IPC_HANDLE_POLL_MSG
trên một tay điều khiển kênh cụ thể. Tuỳ thuộc vào giao thức cụ thể, dành riêng cho dịch vụ, dịch vụ Trusty có thể gửi một hoặc nhiều tin nhắn trả lời được phân phối lại cho bên không an toàn và được đặt vào hàng đợi tin nhắn chỉ số mô tả tệp kênh thích hợp để lệnh gọi read()
của ứng dụng không gian người dùng truy xuất.
tipc_connect()
Mở một nút thiết bị tipc
đã chỉ định và bắt đầu kết nối với một dịch vụ Trusty đã chỉ định.
int tipc_connect(const char *dev_name, const char *srv_name);
[trong] dev_name
: Đường dẫn đến nút thiết bị IPC đáng tin cậy cần mở
[trong] srv_name
: Tên của một dịch vụ Trusty đã xuất bản để kết nối
[retval]: Chỉ số mô tả tệp hợp lệ nếu thành công, -1 nếu không.
tipc_close()
Đóng kết nối với dịch vụ Trusty do chỉ số mô tả tệp chỉ định.
int tipc_close(int fd);
[trong] fd
: Chỉ số mô tả tệp đã được mở trước đó bằng lệnh gọi tipc_connect()
Kernel Trusty IPC Client API
API ứng dụng IPC Trusty của nhân hệ điều hành có sẵn cho các trình điều khiển nhân hệ điều hành. API Trusty IPC của không gian người dùng được triển khai trên API này.
Nhìn chung, cách sử dụng thông thường của API này bao gồm một phương thức gọi tạo đối tượng struct tipc_chan
bằng cách sử dụng hàm tipc_create_channel()
, sau đó sử dụng lệnh gọi tipc_chan_connect()
để bắt đầu kết nối với dịch vụ Trusty IPC chạy ở phía bảo mật. Bạn có thể chấm dứt kết nối với phía từ xa bằng cách gọi tipc_chan_shutdown()
, theo sau là tipc_chan_destroy()
để dọn dẹp tài nguyên.
Khi nhận được thông báo (thông qua lệnh gọi lại handle_event()
) rằng một kết nối đã được thiết lập thành công, phương thức gọi sẽ thực hiện những việc sau:
- Lấy vùng đệm thông báo bằng lệnh gọi
tipc_chan_get_txbuf_timeout()
- Soạn một thông báo và
- Thêm thông báo vào hàng đợi bằng phương thức
tipc_chan_queue_msg()
để phân phối đến một dịch vụ Trusty (ở phía bảo mật) mà kênh được kết nối
Sau khi xếp hàng thành công, phương thức gọi sẽ quên bộ đệm thông báo vì bộ đệm thông báo cuối cùng sẽ quay lại nhóm bộ đệm trống sau khi xử lý phía từ xa (để sử dụng lại sau này, cho các thông báo khác). Người dùng chỉ cần gọi tipc_chan_put_txbuf()
nếu không thể đưa bộ đệm đó vào hàng đợi hoặc không cần thiết nữa.
Người dùng API nhận thông báo từ phía xa bằng cách xử lý lệnh gọi lại thông báo handle_msg()
(được gọi trong ngữ cảnh của hàng đợi công việc trusty-ipc rx
) cung cấp con trỏ đến vùng đệm rx
chứa thông báo đến cần xử lý.
Dự kiến, việc triển khai lệnh gọi lại handle_msg()
sẽ trả về con trỏ đến một struct tipc_msg_buf
hợp lệ.
Vùng đệm này có thể giống với vùng đệm tin nhắn đến nếu được xử lý cục bộ và không cần thiết nữa. Ngoài ra, vùng đệm này có thể là vùng đệm mới được lấy bằng lệnh gọi tipc_chan_get_rxbuf()
nếu vùng đệm đến được đưa vào hàng đợi để xử lý thêm. Bạn phải theo dõi vùng đệm rx
đã tách và cuối cùng là giải phóng vùng đệm đó bằng lệnh gọi tipc_chan_put_rxbuf()
khi không còn cần đến nữa.
Các phương thức trong API ứng dụng IPC Kernel Trusty
tipc_create_channel()
Tạo và định cấu hình một thực thể của kênh IPC đáng tin cậy cho một thiết bị đáng tin cậy-ipc cụ thể.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[trong] dev
: Con trỏ đến trusty-ipc mà kênh thiết bị được tạo
[trong] ops
: Con trỏ đến struct tipc_chan_ops
, với các lệnh gọi lại dành riêng cho phương thức gọi được điền sẵn
[trong] cb_arg
: Con trỏ đến dữ liệu được truyền đến lệnh gọi lại tipc_chan_ops
[retval]: Con trỏ đến một thực thể mới tạo của struct tipc_chan
nếu thành công, ERR_PTR(err)
nếu không
Nói chung, phương thức gọi phải cung cấp hai lệnh gọi lại được gọi không đồng bộ khi hoạt động tương ứng đang diễn ra.
Sự kiện void (*handle_event)(void *cb_arg, int event)
được gọi để thông báo cho phương thức gọi về thay đổi trạng thái kênh.
[trong] cb_arg
: Con trỏ đến dữ liệu được truyền đến lệnh gọi tipc_create_channel()
[trong] event
: Một sự kiện có thể là một trong các giá trị sau:
TIPC_CHANNEL_CONNECTED
– cho biết đã kết nối thành công với bên từ xaTIPC_CHANNEL_DISCONNECTED
– cho biết bên từ xa đã từ chối yêu cầu kết nối mới hoặc yêu cầu ngắt kết nối cho kênh đã kết nối trước đóTIPC_CHANNEL_SHUTDOWN
– cho biết bên từ xa đang tắt, chấm dứt vĩnh viễn tất cả các kết nối
Lệnh gọi lại struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
được gọi để thông báo rằng một thông báo mới đã được nhận qua một kênh đã chỉ định:
- [trong]
cb_arg
: Con trỏ đến dữ liệu được truyền đến lệnh gọitipc_create_channel()
- [trong]
mb
: Con trỏ đếnstruct tipc_msg_buf
mô tả một tin nhắn đến - [retval]: Quá trình triển khai lệnh gọi lại dự kiến sẽ trả về một con trỏ đến
struct tipc_msg_buf
. Đây có thể là cùng một con trỏ được nhận dưới dạng tham sốmb
nếu thông báo được xử lý cục bộ và không còn bắt buộc nữa (hoặc có thể là vùng đệm mới nhận được qua lệnh gọitipc_chan_get_rxbuf()
)
tipc_chan_connect()
Bắt đầu kết nối với dịch vụ IPC đáng tin cậy được chỉ định.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[trong] chan
: Con trỏ đến một kênh do lệnh gọi tipc_create_chan()
trả về
[trong] port
: Con trỏ đến một chuỗi chứa tên dịch vụ cần kết nối
[retval]: 0 nếu thành công, lỗi âm nếu không
Phương thức gọi được thông báo khi kết nối được thiết lập bằng cách nhận lệnh gọi lại handle_event
.
tipc_chan_shutdown()
Chấm dứt kết nối với dịch vụ Trusty IPC do lệnh gọi tipc_chan_connect()
khởi tạo trước đó.
int tipc_chan_shutdown(struct tipc_chan *chan);
[trong] chan
: Con trỏ đến một kênh do lệnh gọi tipc_create_chan()
trả về
tipc_chan_destroy()
Huỷ bỏ một kênh IPC Trusty đã chỉ định.
void tipc_chan_destroy(struct tipc_chan *chan);
[trong] chan
: Con trỏ đến một kênh do lệnh gọi tipc_create_chan()
trả về
tipc_chan_get_txbuf_timeout()
Lấy vùng đệm thông báo có thể dùng để gửi dữ liệu qua một kênh đã chỉ định. Nếu vùng đệm không có sẵn ngay lập tức, thì phương thức gọi có thể bị chặn trong khoảng thời gian chờ đã chỉ định (tính bằng mili giây).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[trong] chan
: Con trỏ đến kênh cần đưa thông báo vào hàng đợi
[trong] chan
: Thời gian chờ tối đa để đợi cho đến khi có vùng đệm tx
[retval]: Vùng đệm thông báo hợp lệ khi thành công, ERR_PTR(err)
khi gặp lỗi
tipc_chan_queue_msg()
Thêm một thông báo vào hàng đợi để gửi qua các kênh Trusty IPC được chỉ định.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[trong] chan
: Con trỏ đến kênh để đưa thông báo vào hàng đợi
[trong] Con trỏ mb:
đến thông báo cần đưa vào hàng đợi
(thu được bằng lệnh gọi tipc_chan_get_txbuf_timeout()
)
[retval]: 0 nếu thành công, lỗi âm nếu không
tipc_chan_put_txbuf()
Giải phóng vùng đệm thông báo Tx
đã chỉ định mà trước đó đã được lệnh gọi tipc_chan_get_txbuf_timeout()
lấy.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[trong] chan
: Con trỏ đến kênh chứa vùng đệm thông báo này
[trong] mb
: Con trỏ đến vùng đệm thông báo để phát hành
[retval]: Không có
tipc_chan_get_rxbuf()
Lấy vùng đệm thông báo mới có thể dùng để nhận thông báo qua kênh đã chỉ định.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[trong] chan
: Con trỏ đến kênh chứa vùng đệm thông báo này
[retval]: Vùng đệm thông báo hợp lệ khi thành công, ERR_PTR(err)
khi gặp lỗi
tipc_chan_put_rxbuf()
Giải phóng vùng đệm thông báo đã chỉ định mà trước đó lệnh gọi tipc_chan_get_rxbuf()
đã lấy.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[trong] chan
: Con trỏ đến kênh chứa vùng đệm thông báo này
[trong] mb
: Con trỏ đến vùng đệm thông báo để phát hành
[retval]: Không có