توفّر Trusty واجهات برمجة تطبيقات لتطوير فئتين من التطبيقات والخدمات:
- التطبيقات والخدمات الموثوق بها التي تعمل على معالج TEE
- التطبيقات العادية وغير الموثوق بها التي تعمل على المعالج الرئيسي وتستخدم الخدمات التي تقدّمها التطبيقات الموثوق بها
تصف واجهة برمجة التطبيقات Trusty بشكل عام نظام Trusty للتواصل بين العمليات (IPC)، بما في ذلك الاتصالات مع العالم غير الآمن. يمكن للبرامج التي تعمل على المعالج الرئيسي استخدام واجهات برمجة التطبيقات Trusty APIs للاتصال بالتطبيقات والخدمات الموثوق بها وتبادل الرسائل العشوائية معها تمامًا مثل خدمة الشبكة عبر بروتوكول الإنترنت. ويعود الأمر للتطبيق في تحديد تنسيق البيانات والدلالات لهذه الرسائل باستخدام بروتوكول على مستوى التطبيق. تضمن البنية الأساسية لنظام التشغيل Trusty إرسال الرسائل بشكل موثوق (باستخدام برامج التشغيل التي تعمل على المعالج الرئيسي)، ويكون الاتصال غير متزامن تمامًا.
المنافذ والقنوات
تستخدم تطبيقات Trusty المنافذ لعرض نقاط نهاية الخدمة في شكل مسار مُعنوَن يتصل به العملاء. يمنح ذلك معرّف خدمة بسيطًا يستند إلى سلسلة يمكن للعملاء استخدامه. اتّبِع أسلوب التسمية في نظام أسماء النطاقات العكسي، مثلاً com.google.servicename
.
عندما يتصل العميل بمنفذ، يتلقّى العميل قناة للتفاعل مع الخدمة. يجب أن تقبل الخدمة اتصالاً واردًا، وعند قبوله، تتلقّى الخدمة أيضًا قناة. في الأساس، تُستخدَم المنافذ للبحث عن الخدمات، ثم يتم الاتصال عبر زوج من القنوات المتصلة (أي حالات الربط على منفذ معيّن). عندما يتصل جهاز عميل بمنفذ، يتم إنشاء اتصال ثنائي الاتجاه ومتماثل. باستخدام مسار الإرسال والاستقبال المتزامن هذا، يمكن للعملاء والخوادم تبادل رسائل عشوائية إلى أن يقرر أي من الجانبين إنهاء الاتصال.
لا يمكن إلا للتطبيقات الموثوق بها في الجانب الآمن أو وحدات Trusty kernel إنشاء المنافذ. لا يمكن للتطبيقات التي تعمل على الجانب غير الآمن (في العالم العادي) الاتصال إلا بالخدمات التي ينشرها الجانب الآمن.
استنادًا إلى المتطلبات، يمكن أن يكون التطبيق الموثوق به عميلًا وخادمًا في الوقت نفسه. قد يحتاج التطبيق الموثوق الذي ينشر خدمة (بصفته خادمًا) إلى الاتصال بخدمات أخرى (بصفته عميلًا).
واجهة برمجة التطبيقات Handle
العناوين هي أعداد صحيحة غير موقَّعة تمثّل موارد مثل المنافذ والقنوات، على غرار أوصاف الملفات في نظام التشغيل UNIX. بعد إنشاء الأسماء المعرِّفة، يتم وضعها في جدول أسماء معرِّفة خاص بالتطبيق ويمكن الرجوع إليها لاحقًا.
يمكن للمتصل ربط البيانات الخاصة باسم معرِّف باستخدام
طريقة set_cookie()
.
الطرق في واجهة برمجة التطبيقات Handle API
تكون الأسماء المعرِّفة صالحة في سياق تطبيق فقط. يجب ألا يمرِّر التطبيق قيمة الاسم المعرِّف إلى تطبيقات أخرى ما لم يتم تحديد ذلك صراحةً. يجب تفسير قيمة الاسم المعرِّف فقط من خلال مقارنتها
بـ INVALID_IPC_HANDLE #define,
التي يمكن للتطبيق استخدامها كإشارة
إلى أنّ الاسم المعرِّف غير صالح أو لم يتم ضبطه.
set_cookie()
ربط البيانات الخاصة التي يقدّمها المتصل باسم معرِّف محدّد
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: أيّ معرّف يعرضه أحد طلبات البيانات من واجهة برمجة التطبيقات
[in] cookie
: مؤشر إلى بيانات عشوائية في مساحة المستخدم في تطبيق Trusty
[retval]: NO_ERROR
عند النجاح، < 0
رمز الخطأ في حال عدم النجاح
يكون هذا الطلب مفيدًا لمعالجة الأحداث عند حدوثها في وقت لاحق بعد إنشاء الاسم المعرِّف. توفّر آلية معالجة الأحداث المعرّف وملف تعريف الارتباط الخاص به مرة أخرى إلى معالج الأحداث.
يمكن انتظار الأسماء المعرِّفة للأحداث باستخدام طلب wait()
.
wait()
تنتظر حدوث حدث في اسم معيّن لفترة زمنية محدّدة.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: أيّ معرّف يعرضه أحد طلبات البيانات من واجهة برمجة التطبيقات
[out] event
: مؤشر إلى البنية التي تمثّل
حدثًا وقع على هذا الاسم المعرِّف
[in] timeout_msecs
: قيمة المهلة بالملي ثانية. قيمة -1 تعني مهلة غير محدودة.
[retval]: NO_ERROR
إذا حدث حدث صالح خلال فاصل مهلة محدّد، ERR_TIMED_OUT
إذا انتهت مهلة محدّدة ولكن لم يحدث
أي حدث، < 0
للأخطاء الأخرى
عند نجاح الإجراء (retval == NO_ERROR
)، تملأ عملية طلب wait()
uevent_t
بنية uevent_t
محدّدة بمعلومات عن الحدث الذي حدث.
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;
يحتوي الحقل event
على مجموعة من القيم التالية:
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
- ما مِن أحداث في انتظار المراجعة،
على المُتصل إعادة الانتظار.
IPC_HANDLE_POLL_ERROR
- حدث خطأ داخلي غير محدّد
IPC_HANDLE_POLL_READY
- يعتمد ذلك على نوع الاسم المعرِّف، على النحو التالي:
- بالنسبة إلى المنافذ، تشير هذه القيمة إلى أنّ هناك عملية ربط في انتظار المراجعة.
- بالنسبة إلى القنوات، تشير هذه القيمة إلى أنّه تمّ إنشاء اتصال غير متزامن
(راجِع
connect()
).
لا تنطبق الأحداث التالية إلا على القنوات:
IPC_HANDLE_POLL_HUP
- يشير إلى أنّ أحد المستخدمين قد أغلق القناةIPC_HANDLE_POLL_MSG
- يشير إلى أنّ هناك رسالة في انتظار المراجعة لهذه القناةIPC_HANDLE_POLL_SEND_UNBLOCKED
- يشير إلى أنّه قد يحاول متصل تم حظر إرسال رسائله في السابق إرسال رسالة مرة أخرى (اطّلِع على وصفsend_msg()
لمعرفة التفاصيل)
يجب أن يكون معالِج الأحداث مستعدًا للتعامل مع مجموعة من الأحداث المحدّدة، لأنّه قد يتم ضبط عدة بتات في الوقت نفسه. على سبيل المثال، بالنسبة إلى القناة، من الممكن أن تتضمّن رسائل في انتظار المراجعة، وأن يغلق أحد المشاركين الاتصال في الوقت نفسه.
تكون معظم الأحداث ثابتة. وتبقى هذه الطلبات سارية ما دام الشرط الأساسي
ساريًا (على سبيل المثال، يتم استلام جميع الرسائل في انتظار المراجعة ومعالجة طلبات الربط في انتظار المراجعة). ويُستثنى من ذلك حالة حدث
IPC_HANDLE_POLL_SEND_UNBLOCKED
الذي يتم cleared عند قراءته ولا يملك التطبيق سوى فرصة واحدة لمعالجة
الحدث.
يمكن إلغاء الأسماء المعرِّفة من خلال استدعاء الطريقة close()
.
close()
تؤدي هذه الوظيفة إلى تدمير المورد المرتبط بالاسم المعرِّف المحدّد وإزالته من جدول الأسماء المعرِّفة.
long close(uint32_t handle_id);
[in] handle_id
: الاسم المعرِّف للعنصر المطلوب إزالته
[retval]: 0 في حال النجاح، خطأ سالب في حال عدم النجاح
Server API
يبدأ الخادم بإنشاء منافذ مُسمّاة واحدة أو أكثر تمثّل نقاط نهاية الخدمة. يتم تمثيل كل منفذ باسم معرِّف.
الطرق في Server API
port_create()
تنشئ منفذ خدمة مُسمّى.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: اسم سلسلة المنفذ (كما هو موضّح أعلاه). يجب أن يكون
هذا الاسم فريدًا في النظام، ولن تنجح محاولات إنشاء نسخة مكرّرة منه.
[in] num_recv_bufs
: الحد الأقصى لعدد وحدات التخزين المؤقت التي يمكن للقناة على
هذا المنفذ تخصيصها مسبقًا لتسهيل تبادل البيانات مع العميل. يتم احتساب المخزن المؤقت
بشكل منفصل للبيانات التي تنتقل في كلا الاتجاهين، لذا فإنّ تحديد 1 هنا يعني أنّه تم تخصيص ملف تخزين مؤقت واحد
للإرسال وملف تخزين مؤقت واحد للاستقبال مسبقًا. بشكل عام، يعتمد عدد وحدات التخزين المؤقت
المطلوبة على اتفاقية البروتوكول ذات المستوى الأعلى بين العميل
والخادم. يمكن أن يكون العدد أقل من 1 في حال استخدام بروتوكول متزامن للغاية
(إرسال رسالة، وتلقّي ردّ قبل إرسال رسالة أخرى). ولكن يمكن أن يكون العدد
أكبر إذا كان العميل يتوقّع إرسال أكثر من رسالة واحدة قبل أن يظهر ردّ (مثل رسالة واحدة كمقدّمة ورسالة أخرى كطلب فعلي). يتم تخصيص مجموعات الفواصل لكل قناة، لذا سيكون لكل اتصالَين منفصلَين (قناتَين)
مجموعات فواصل منفصلة.
[in] recv_buf_size
: الحد الأقصى لحجم كل ذاكرة تخزين مؤقت فردية في
مجموعة ذاكرات التخزين المؤقت أعلاه. تعتمد هذه القيمة
على البروتوكول وتحدّ بشكل فعّال من الحد الأقصى لحجم الرسالة الذي يمكنك تبادله
مع نظيرك.
[in] flags
: مجموعة من العلامات التي تحدّد سلوك المنفذ الإضافي
يجب أن تكون هذه القيمة مزيجًا من القيم التالية:
IPC_PORT_ALLOW_TA_CONNECT
- السماح بالاتصال من تطبيقات آمنة أخرى
IPC_PORT_ALLOW_NS_CONNECT
- السماح بإجراء اتصال من بيئة غير آمنة
[retval]: معرّف المنفذ الذي تم إنشاؤه إذا لم يكن سالبًا أو خطأ محدّد إذا كان سالبًا
بعد ذلك، يفحص الخادم قائمة أسماء المنفذ بحثًا عن الاتصالات الواردة باستخدام طلب wait()
. عند تلقّي طلب اتصال
يُشار إليه من خلال ضبط القيمة IPC_HANDLE_POLL_READY
في الحقل
event
من بنية uevent_t
، يجب أن يطلب العميلaccept()
من السيرفر إنهاء عملية إنشاء الاتصال وإنشاء قناة (ممثَّلة باسم معرِّف آخر) يمكن بعد ذلك إجراء استطلاع عليها بحثًا عن الرسائل الواردة.
accept()
يقبل عملية ربط واردة ويحصل على اسم معرِّف للقناة.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: المعرّف الذي يمثّل المنفذ الذي ربط إليه العميل
[out] peer_uuid
: مؤشر إلى بنية uuid_t
التي سيتم
ملؤها بمعرّف UUID لتطبيق العميل المتصل. يتم
ضبطه على كل الأصفار إذا كان الاتصال مصدره من المجال غير الآمن
[retval]: معرّف قناة (إذا لم يكن سالبًا) يمكن للّخادم من خلاله تبادل الرسائل مع العميل (أو رمز خطأ في حال عدم توفّر قناة)
Client API
يحتوي هذا القسم على الطرق الواردة في Client API.
الطرق في Client API
connect()
يبدأ الاتصال بمنفذ محدّد بالاسم.
long connect(const char *path, uint flags);
[in] path
: اسم منفذ نشره تطبيق موثوق به
[in] flags
: لتحديد سلوك اختياري إضافي
[retval]: الاسم المعرِّف لقناة يمكن تبادل الرسائل من خلالها مع الخادم، وخطأ إذا كان سالبًا
في حال عدم تحديد flags
(يتم ضبط المَعلمة flags
على 0)، يؤدي استدعاء connect()
إلى بدء اتصال متزامن
بمنفذ محدّد يعرض خطأً على الفور
في حال عدم توفّر المنفذ، وينشئ حظرًا إلى أن يقبل
الخادم الاتصال بخلاف ذلك.
يمكن تغيير هذا السلوك من خلال تحديد مجموعة من قيمتَين، описанتَين أدناه:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
- يفرض على connect()
الانتظار إذا لم يكن المنفذ المحدّد متوفّرًا على الفور عند التنفيذ، بدلاً من تعذُّر التنفيذ على الفور.
IPC_CONNECT_ASYNC
- في حال ضبطه، يبدأ اتصالًا غير متزامن. على التطبيق فحص قيمة العنصر المرجعي المعروضة من خلال استدعاء wait()
لرصد حدث اكتمال الاتصال الذي يشير إليه بتعيين القيمة IPC_HANDLE_POLL_READY
في حقل الحدث من بنية uevent_t
قبل بدء التشغيل العادي.
Messaging API
تتيح طلبات Messaging API إرسال الرسائل وقراءتها من خلال اتصال (قناة) تم إنشاؤه سابقًا. إنّ طلبات البيانات من Messaging API هي نفسها للخوادم والعملاء.
يتلقّى العميل اسمًا معرِّفًا للقناة من خلال إصدار connect()
طلب، ويحصل الخادم على اسم معرِّف للقناة من طلب accept()
،
كما هو موضّح أعلاه.
بنية رسالة Trusty
كما هو موضّح في ما يلي، تتضمّن الرسائل التي تتبادلها Trusty API بنية محدودة ، ما يترك للخادم والعميل الاتفاق على الدلالات المتعلقة بالمحتوى الحقيقي:
/* * 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;
يمكن أن تتألف الرسالة من وحدة تخزين مؤقت واحد أو أكثر غير متّصلة يمثّلها
صفيف من بنى iovec_t
. ينفِّذ Trusty عملية جمع/تشتيت
يقرأ ويكتب في هذه الكتل
باستخدام صفيف iov
. إنّ محتوى المخزن المؤقت الذي يمكن وصفه
باستخدام مصفوفة iov
هو عشوائي تمامًا.
الطرق في Messaging API
send_msg()
إرسال رسالة عبر قناة محدّدة
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: الاسم المعرِّف للقناة التي سيتم إرسال الرسالة من خلالها
[in] msg
: مؤشر إلى ipc_msg_t structure
الذي يصف الرسالة
[retval]: إجمالي عدد وحدات البايت المُرسَلة في حال النجاح، أو خطأ سلبي في حال عدم النجاح
إذا كان العميل (أو الخادم) يحاول إرسال رسالة عبر القناة ولم يكن هناك مساحة في قائمة انتظار الرسائل المرسَلة إلى الوجهة، قد تنتقل القناة إلى حالة حظر الإرسال (من المفترض ألا يحدث ذلك مطلقًا في حالة استخدام بروتوكول طلبات/ردود متزامن بسيط، ولكن قد يحدث ذلك في حالات أكثر تعقيدًا)، ويُشار إلى ذلك من خلال عرض رمز الخطأ ERR_NOT_ENOUGH_BUFFER
.
في هذه الحالة، على المتصل الانتظار إلى أن يُفرِّغ الخادم المعادل بعض
المساحة في قائمة الانتظار الخاصة بالاستلام من خلال استرداد رسائل المعالجة والإيقاف نهائيًا،
المشار إليها من خلال ضبط القيمة IPC_HANDLE_POLL_SEND_UNBLOCKED
في
حقل event
من بنية uevent_t
التي تُرجعها طلب wait()
.
get_msg()
الحصول على معلومات وصفية عن الرسالة التالية في قائمة انتظار الرسائل الواردة
قناة معيّنة
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: الاسم المعرِّف للقناة التي يجب استرداد رسالة جديدة منها
[out] msg_info
: بنية معلومات الرسالة موضّحة على النحو التالي:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
يتم منح كل رسالة معرّفًا فريدًا في مجموعة الرسائل غير المُستلَمة، ويتم ملء إجمالي طول كل رسالة. يمكن أن تكون هناك رسائل مُرسَلة (مفتوحة) متعدّدة في آنٍ واحد لقناة معيّنة، إذا تم ضبط الإعدادات والسماح بذلك من خلال الprotocol.
[retval]: NO_ERROR
عند النجاح، خطأ سلبي في حال عدم النجاح
read_msg()
قراءة محتوى الرسالة التي تحمل المعرّف المحدّد بدءًا من الموضع المحدّد
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: الاسم المعرِّف للقناة التي تريد قراءة الرسالة منها
[in] msg_id
: معرّف الرسالة المطلوب قراءتها
[in] offset
: الإزاحة في الرسالة التي تريد بدء القراءة منها
[out] msg
: مؤشر إلى بنية ipc_msg_t
التي تصف
مجموعة من المخازن المؤقتة لتخزين بيانات الرسائل القادمة
[retval]: إجمالي عدد وحدات البايت المخزّنة في ملفّات تخزين مؤقت msg
عند
النجاح، وخطأ سلبي في حال عدم النجاح
يمكن استدعاء الطريقة read_msg
عدة مرات بدءًا من
إزاحة مختلفة (وليس بالضرورة
متسلسلة) حسب الحاجة.
put_msg()
تُوقِف هذه الدالة رسالة ذات معرّف محدّد.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: الاسم المعرِّف للقناة التي وصلت إليها الرسالة
[in] msg_id
: معرّف الرسالة التي يتم إيقافها نهائيًا
[retval]: NO_ERROR
عند النجاح، خطأ سلبي في حال عدم النجاح
لا يمكن الوصول إلى محتوى الرسالة بعد إيقافها نهائيًا وتحرير ملف التخزين المؤقت الذي كانت تشغله.
File Descriptor API
تتضمّن File Descriptor API طلبات البيانات read()
وwrite()
وioctl()
. يمكن أن تعمل كل هذه الطلبات على مجموعة محدّدة مسبقًا (ثابتة) من وصفي
الملفات التي يتم تمثيلها عادةً بأرقام صغيرة. في التنفيذ الحالي، تكون مساحة معرّف الملف منفصلة عن مساحة معرّف IPC. تشبه واجهة برمجة التطبيقات File Descriptor API في Trusty
واجهة برمجة التطبيقات التقليدية المستندة إلى ملف الوصف.
تتوفّر تلقائيًا 3 أوصاف ملفات محدّدة مسبقًا (معروفة وعادية):
- 0 - إدخال معياري إنّ التنفيذ التلقائي للدخل العادي
fd
هو إجراء لا يؤدي إلى أيّ ناتج (لأنّه من غير المتوقّع أن تتضمّن التطبيقات الموثوق بها كونسولاً تفاعليًا)، لذا فإنّ قراءةioctl()
أو كتابته أو استدعائه علىfd
0 من المفترض أن يؤدي إلى ظهور خطأERR_NOT_SUPPORTED
. - 1 - الإخراج العادي يمكن توجيه البيانات المكتوبة في الإخراج العادي (استنادًا
إلى مستوى تصحيح الأخطاء في LK) إلى UART و/أو سجلّ ذاكرة متاح على الجانب
غير الآمن، وذلك استنادًا إلى النظام الأساسي والإعداد. يجب أن تظهر سجلات تصحيح الأخطاء والرسائل غير المُهمّة في الإخراج العادي. إنّ الطريقتَين
read()
وioctl()
لا تؤديان إلى أيّ إجراء، ومن المفترض أن تعرضا الخطأERR_NOT_SUPPORTED
. - 2 - الخطأ المعياري يجب توجيه البيانات المكتوبة في الخطأ العادي إلى UART
أو سجلّ الذاكرة المتاح على الجانب غير الآمن، وذلك استنادًا إلى النظام الأساسي
والإعدادات. ننصح بعدم كتابة سوى الرسائل المُهمّة في تدفق الصعوبات العادي، لأنّه من المرجّح جدًا أن يكون هذا التدفق غير محدود. إنّ الطريقتَين
read()
وioctl()
لا تؤديان إلى أيّ إجراء ومن المفترض أن تعرضا الخطأERR_NOT_SUPPORTED
.
على الرغم من أنّه يمكن توسيع هذه المجموعة من أوصاف الملفات لتنفيذ المزيد من
fds
(لتنفيذ الإضافات الخاصة بالنظام الأساسي)، يجب توخي الحذر عند توسيع أوصاف الملفات. إنّ توسيع أوصاف الملفات معرّض لإنشاء
تعارضات، ولا يُنصح به بشكل عام.
الطرق في File Descriptor API
read()
تحاول قراءة ما يصل إلى count
بايت من البيانات من وصف ملف محدّد.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: واصِف الملف الذي سيتمّ الاطّلاع عليه
[out] buf
: مؤشر إلى مخزن مؤقت لتخزين البيانات فيه
[in] count
: الحد الأقصى لعدد البايتات التي سيتم قراءتها
[retval]: عدد وحدات البايت المقروءة، أو خطأ سالب في حال عدم القراءة
write()
تكتب ما يصل إلى count
بايت من البيانات في ملف وصف ملف محدّد.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: واصِف الملف المطلوب الكتابة إليه
[out] buf
: مؤشر إلى البيانات المطلوب كتابتها
[in] count
: الحد الأقصى لعدد البايتات المطلوب كتابتها
[retval]: عدد وحدات البايت التي تم كتابتها، أو خطأ سالب في حال عدم الكتابة
ioctl()
يؤدي إلى استدعاء أمر ioctl
محدّد لوصف ملف معيّن.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: واصِف الملف الذي سيتم استدعاء ioctl()
عليه
[in] cmd
: الأمر ioctl
[in/out] args
: مؤشر إلى وسيطات ioctl()
Miscellaneous API
الطرق في Miscellaneous API
gettime()
لعرض وقت النظام الحالي (بالنانو ثانية).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: يعتمد على النظام الأساسي، وأدخِل صفرًا للإعداد التلقائي
[in] flags
: محجوز، يجب أن يكون صفرًا
[out] time
: مؤشر إلى قيمة int64_t
لتخزين الوقت الحالي فيها
[retval]: NO_ERROR
عند النجاح، خطأ سلبي في حال عدم النجاح
nanosleep()
تعليق تنفيذ تطبيق الاتصال لفترة زمنية محدّدة واستئنافه بعد هذه الفترة
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: محجوز، يجب أن يكون صفرًا
[in] flags
: محجوز، يجب أن يكون صفرًا
[in] sleep_time
: مدة النوم بالنانوثواني
[retval]: NO_ERROR
عند النجاح، خطأ سلبي في حال عدم النجاح
مثال على خادم تطبيقات موثوق به
يوضِّح نموذج التطبيق التالي استخدام واجهات برمجة التطبيقات أعلاه. ينشئ المثال خدمة "صدى" تعالج اتصالات واردة متعددة ويعيد إلى المتصل جميع الرسائل التي يتلقّاها من العملاء المنشأين من الجانب الآمن أو غير الآمن.
#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; }
تُرسِل الطريقة run_end_to_end_msg_test()
10,000 رسالة بشكل غير متزامن
إلى خدمة "echo" وتعالج
الردود.
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; }
التطبيقات وواجهات برمجة التطبيقات غير الآمنة في جميع أنحاء العالم
يمكن الوصول إلى مجموعة من خدمات Trusty، التي تم نشرها من الجانب الآمن وتم وضع علامة عليها باستخدام سمة IPC_PORT_ALLOW_NS_CONNECT
، من خلال ملف التمهيد
وبرامج مساحة المستخدم التي تعمل على
الجانب غير الآمن.
تختلف بيئة التنفيذ في الجانب غير الآمن (النواة ومساحة المستخدم) اختلافًا كبيرًا عن بيئة التنفيذ في الجانب الآمن. لذلك، بدلاً من مكتبة واحدة لكلتا البيئتَين، تتوفّر مجموعتَان مختلفتَان من واجهات برمجة التطبيقات. في kernel، يوفّر ملف Client API منصة التشغيل trusty-ipc kernel driver ويُسجِّل عقدة جهاز حرفي يمكن استخدامها من قِبل عمليات مساحة المستخدم للتواصل مع الخدمات التي تعمل على الجانب الآمن.
واجهة برمجة تطبيقات Trusty IPC Client في مساحة المستخدم
مكتبة Trusty IPC Client API في مساحة المستخدم هي طبقة رقيقة فوق
عقدة الجهاز fd
.
يبدأ برنامج مساحة المستخدم جلسة تواصل
من خلال استدعاء tipc_connect()
،
وبدء اتصال بخدمة Trusty محدّدة. داخليًا،
يفتح طلب tipc_connect()
عقدة جهاز محدّدة للحصول على وصف ملف ويُجري طلب TIPC_IOC_CONNECT ioctl()
مع المَعلمة argp
التي تشير إلى سلسلة تحتوي على اسم الخدمة المطلوب الاتصال بها.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
لا يمكن استخدام وصف الملف الناتج إلا للتواصل مع الخدمة
التي تم إنشاؤه لها. يجب إغلاق وصف الملف من خلال
استدعاء tipc_close()
عندما لا يكون الاتصال مطلوبًا بعد الآن.
يتصرف وصف الملف الذي تم الحصول عليه من خلال طلب tipc_connect()
كعقدة جهاز أحرف عادية، ويكون وصف الملف:
- يمكن التبديل إلى وضع عدم الحظر إذا لزم الأمر
- يمكن الكتابة إليه باستخدام مكالمة
write()
عادية لإرسال الرسائل إلى الجانب الآخر - يمكن إجراء استطلاع (باستخدام طلبات
poll()
أوselect()
) لمعرفة مدى توفّر الرسائل الواردة كملف وصفي عادي - يمكن قراءتها لاسترداد الرسائل الواردة
يُرسِل المتصل رسالة إلى خدمة Trusty من خلال تنفيذ طلب كتابة لfd
المحدّد. يحوّل برنامج تشغيل trusty-ipc جميع البيانات التي يتم تمريرها إلى طلب write()
أعلاه
إلى رسالة. يتم تسليم الرسالة
إلى الجانب الآمن حيث يعالج النظام الفرعي لبروتوكول "التواصل بين العمليات" (IPC) البيانات في
النواة Trusty ويتم توجيهها إلى الوجهة المناسبة وتسليمها إلى حلقة أحداث
التطبيق كحدث IPC_HANDLE_POLL_MSG
على معرّف قناة معيّن. استنادًا إلى بروتوكول معيّن
خاص بالخدمة، قد ترسل خدمة Trusty رسالة رد واحدة أو أكثر
يتم تسليمها مرة أخرى إلى الجانب غير الآمن ويتم وضعها في ملف وصف ملف القناة المناسب
قائمة الانتظار التي يمكن للمستخدم استردادها
باستخدام مكالمة read()
لتطبيق المساحة.
tipc_connect()
يفتح عقدة جهاز tipc
محدّدة ويُنشئ اتصالاً
بخدمة Trusty محدّدة.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: مسار عقدة جهاز Trusty IPC المطلوب فتحها
[in] srv_name
: اسم خدمة Trusty المنشورة المطلوب الاتصال بها
[retval]: ملف وصف صالح في حال النجاح، -1 في حال عدم النجاح
tipc_close()
يُغلق هذا الإجراء الاتصال بخدمة Trusty المحدَّدة بواسطة ملف وصفي.
int tipc_close(int fd);
[in] fd
: واصِف الملف الذي تم فتحه سابقًا من خلال
مكالمة tipc_connect()
واجهة برمجة التطبيقات لبرنامج Kernel Trusty IPC Client
تتوفّر واجهة برمجة التطبيقات Trusty IPC Client API الخاصة بالنواة لبرامج تشغيل النواة. يتم تنفيذ واجهة برمجة التطبيقات Trusty IPC API الخاصة بمساحة المستخدم على مستوى أعلى من هذه الواجهة.
بشكل عام، يتألف الاستخدام المعتاد لواجهة برمجة التطبيقات هذه من إنشاء المُتصل لموضوع struct tipc_chan
باستخدام الدالة tipc_create_channel()
ثم استخدام طلب tipc_chan_connect()
لبدء اتصال
بخدمة Trusty IPC التي تعمل على الجانب
المؤمّن. يمكن إنهاء الاتصال بالجانب البعيد من خلال
طلب tipc_chan_shutdown()
متبوعًا بمحاولة
tipc_chan_destroy()
لتنظيف الموارد.
عند تلقّي إشعار (من خلال handle_event()
طلب معاودة الاتصال)
بأنّه تمّ الاتصال بنجاح، ينفّذ المتصل
ما يلي:
- الحصول على ذاكرة تخزين مؤقت للرسائل باستخدام طلب
tipc_chan_get_txbuf_timeout()
- إنشاء رسالة
- إضافة الرسالة إلى "قائمة الانتظار" باستخدام طريقة
tipc_chan_queue_msg()
لإرسالها إلى خدمة Trusty (على الجانب الآمن) التي تم ربط channel بها
بعد إضافة الرسالة إلى "قائمة الانتظار" بنجاح، يجب أن ينسى المُتصل ذاكرة التخزين المؤقت للرسالة، لأنّ ذاكرة التخزين المؤقت للرسالة تعود في النهاية إلى مجموعة ذاكرات التخزين المؤقت المتاحة بعد معالجتها من قِبل الجانب البعيد (لإعادة استخدامها لاحقًا مع الرسائل الأخرى). يحتاج المستخدم
فقط إلى استدعاء tipc_chan_put_txbuf()
في حال تعذّر عليه
إضافة هذا المورد الاحتياطي إلى "قائمة الانتظار" أو إذا لم يعد مطلوبًا.
يتلقّى مستخدم واجهة برمجة التطبيقات رسائل من الجانب البعيد من خلال معالجة handle_msg()
إشعار الاستدعاء (الذي يتم استدعاؤه في سياق "قائمة العمل" rx
في trusty-ipc) الذي يقدّم مؤشرًا إلى rx
مخزن مؤقت يحتوي على rx
رسالة واردة ليتم التعامل معها.
من المتوقّع أن يعرض تنفيذ handle_msg()
مرجعًا إلى struct tipc_msg_buf
صالح.
ويمكن أن يكون مطابقًا لمساحة التخزين المؤقت للرسائل الواردة إذا تم التعامل معها محليًا
وأصبحت غير مطلوبة بعد ذلك. بدلاً من ذلك، يمكن أن يكون مخزنًا مؤقتًا جديدًا تم الحصول عليه من خلال tipc_chan_get_rxbuf()
إذا تم وضع المخزن المؤقت الوافد في قائمة الانتظار لمزيد من المعالجة. يجب تتبُّع مخزن مؤقت منفصل من rx
وإزالته في النهاية باستخدام طلب tipc_chan_put_rxbuf()
عندما
لم يعُد مطلوبًا.
الطرق في واجهة برمجة التطبيقات Kernel Trusty IPC Client API
tipc_create_channel()
لإنشاء مثيل لقناة Trusty IPC وضبطه لجهاز trusty-ipc معيّن
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: مؤشر إلى trusty-ipc الذي تم إنشاء قناة
الجهاز له
[in] ops
: مؤشر إلى struct tipc_chan_ops
،
مع ملء وظائف callback الخاصة بالمتصل
[in] cb_arg
: مؤشر إلى البيانات التي يتم تمريرها
إلى وظائف معاودة الاتصال tipc_chan_ops
[retval]: مؤشر إلى مثيل تم إنشاؤه حديثًا من
struct tipc_chan
في حال النجاح،
ERR_PTR(err)
في حال عدم النجاح
بشكل عام، يجب أن يقدّم المُتصل وظيفتَي استدعاء يتمّ تنفيذهما بشكل غير متزامن عندما يحدث النشاط المقابل.
يتمّ void (*handle_event)(void *cb_arg, int event)
استدعاء الحدث
لإعلام المتصل بتغيير حالة القناة.
[in] cb_arg
: مؤشر إلى البيانات التي تم تمريرها إلى دعوة
tipc_create_channel()
[in] event
: حدث يمكن أن يكون إحدى القيم التالية:
TIPC_CHANNEL_CONNECTED
- يشير إلى اتصال ناجح بالجانب البعيدTIPC_CHANNEL_DISCONNECTED
- يشير إلى أنّ الجهة البعيدة رفضت طلب الربط الجديد أو طلبت إلغاء الربط بالقناة التي سبق ربطهاTIPC_CHANNEL_SHUTDOWN
- يشير إلى أنّ الجهاز البعيد يتم إيقافه، ما يؤدي إلى إنهاء جميع عمليات الربط نهائيًا
يتمّ استدعاء struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
الاستدعاء لتقديم إشعار بأنّه تم تلقّي
رسالة جديدة عبر قناة محدّدة:
- [in]
cb_arg
: مؤشر إلى البيانات التي تم تمريرها إلى دعوةtipc_create_channel()
- [in]
mb
: مؤشر إلىstruct tipc_msg_buf
يصف رسالة واردة - [retval]: من المتوقّع أن يعرض تنفيذ دالة الاستدعاء مؤشرًا إلى
struct tipc_msg_buf
يمكن أن يكون المؤشر نفسه الذي تم تلقّيه كمَعلمةmb
إذا تم التعامل مع الرسالة محليًا ولم يكن مطلوبًا بعد الآن (أو يمكن أن يكون مخزنًا مؤقتًا جديدًا تم الحصول عليه من خلال طلبtipc_chan_get_rxbuf()
)
tipc_chan_connect()
يبدأ الاتصال بخدمة Trusty IPC المحدّدة.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: مؤشر إلى قناة تم عرضها من خلال طلب
tipc_create_chan()
[in] port
: مؤشر إلى سلسلة تحتوي على اسم
الخدمة المطلوب الاتصال بها
[retval]: 0 عند النجاح، خطأ سلبي في حال عدم النجاح
يتم إشعار المتصل عند إجراء اتصال من خلال تلقّي مكالمة handle_event
.
tipc_chan_shutdown()
تُنهي هذه الدالة الاتصال بخدمة Trusty IPC التي تم بدؤها سابقًا
باستدعاء tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: مؤشر إلى قناة تم إرجاعها
بواسطة طلب tipc_create_chan()
tipc_chan_destroy()
يُدمِر قناة Trusty IPC محدّدة.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: مؤشر إلى قناة تم عرضها من خلال طلب
tipc_create_chan()
tipc_chan_get_txbuf_timeout()
تحصل على ذاكرة تخزين مؤقت للرسائل يمكن استخدامها لإرسال البيانات عبر قناة معيّنة. إذا لم يكن المخزن المؤقت متاحًا على الفور، قد يتم حظر المتصل لمدة المهلة المحدّدة (بالملي ثانية).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: مؤشر إلى القناة التي سيتم إدراج رسالة في انتظار المراجعة فيها
[in] chan
: الحد الأقصى لمهلة الانتظار إلى أن يصبح ملف التخزين المؤقت
tx
متاحًا
[retval]: مخزن مؤقت صالح للرسائل في حال النجاح،
ERR_PTR(err)
في حال الخطأ
tipc_chan_queue_msg()
تُضيف رسالة إلى "قائمة الانتظار" لإرسالها عبر قنوات معالجة المعلومات بين العمليات (IPC) في Trusty المحدّدة.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: مؤشر إلى القناة التي سيتم إدراج الرسالة في انتظار البث عليها
[in] mb:
مؤشر إلى الرسالة التي سيتم إدراجها في "قائمة الانتظار"
(يتم الحصول عليه من خلال طلب tipc_chan_get_txbuf_timeout()
)
[retval]: 0 عند النجاح، خطأ سلبي في حال عدم النجاح
tipc_chan_put_txbuf()
تُستخدَم هذه الوظيفة لتحرير وحدة تخزين الرسائل Tx
المحدّدة
التي تم الحصول عليها سابقًا من خلال طلب tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: مؤشر إلى القناة التي تنتمي إليها
ذاكرة التخزين المؤقت للرسائل
[in] mb
: مؤشر إلى وحدة تخزين مؤقت للرسائل لإخلاء ذاكرتها
[retval]: لا شيء
tipc_chan_get_rxbuf()
الحصول على ذاكرة تخزين مؤقت جديد للرسائل يمكن استخدامها لتلقّي الرسائل عبر القناة المحدّدة
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: مؤشر إلى قناة ينتمي إليها مخزن الرسائل المؤقت هذا
[retval]: مخزن مؤقت صالح للرسائل في حال النجاح، ERR_PTR(err)
في حال الخطأ
tipc_chan_put_rxbuf()
تُستخدَم لتحرير مخزن مؤقت محدّد للرسائل تم الحصول عليه سابقًا من خلال مكالمة
tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: مؤشر إلى قناة ينتمي إليها مخزن الرسائل المؤقت هذا
[in] mb
: مؤشر إلى وحدة تخزين مؤقت للرسائل لإخلاء ذاكرتها
[retval]: لا شيء