مرجع واجهة برمجة التطبيقات Trusty API

توفّر Trusty واجهات برمجة تطبيقات لتطوير فئتَين من التطبيقات/الخدمات:

  • التطبيقات أو الخدمات الموثوق بها التي تعمل على معالج TEE
  • يشير هذا المصطلح إلى تطبيقات عادية أو غير موثوق بها تعمل على معالج البيانات الرئيسي وتستخدم الخدمات المتوفّرة. من التطبيقات الموثوق بها

الائتمان تصف واجهة برمجة التطبيقات بشكل عام نظام الاتصال بين العمليات (IPC) ، بما في ذلك التواصل مع العالم غير الآمن. البرامج التي تعمل على يمكن أن يستخدم معالج البيانات الرئيسي واجهات Trusty APIs للاتصال بالتطبيقات/الخدمات الموثوقة وتبادل الرسائل العشوائية معها تمامًا مثل أي خدمة شبكة عبر عنوان IP. ويعتمد التطبيق على تحديد تنسيق البيانات ودلالاتها الرسائل باستخدام بروتوكول على مستوى التطبيق. يُعد التسليم الموثوق للرسائل بضمان من خلال البنية الأساسية لنظام Trusty الأساسي (في شكل برامج تشغيل يعمل على المعالج الرئيسي)، والاتصال غير متزامن تمامًا.

المنافذ والقنوات

تستخدم تطبيقات Trusty المنافذ لعرض نقاط نهاية الخدمة في النموذج لمسار محدد يتصل به العملاء. يعطي هذا رسمًا بسيطًا يستند إلى سلسلة معرّف الخدمة للعملاء لاستخدامها. اصطلاح التسمية هو نمط نظام أسماء النطاقات العكسي التسمية، على سبيل المثال. com.google.servicename

عندما يتصل عميل بمنفذ، يتلقّى العميل قناة للتفاعل. باستخدام الخدمة. يجب أن تقبل الخدمة الاتصال الوارد، ومتى فهو يحصل أيضًا على قناة. تُستخدم المنافذ في الأساس للبحث عن الخدمات ثم يحدث الاتصال عبر قناتين متصلتين (أي حالات الاتصال على المنفذ). عندما يتصل عميل بمنفذ، فالخوارزمية المتماثلة، يتم إنشاء اتصال ثنائي الاتجاه. باستخدام هذا المسار مزدوج الاتجاه، يمكن للعملاء ويمكن للخوادم تبادل الرسائل العشوائية حتى يقرِّر أي من الجانبين تمزيقها الاتصال.

يمكن إنشاء التطبيقات الموثوق بها من الجانب الآمن أو وحدات النواة الموثوقة فقط متعددة. يمكن للتطبيقات التي تعمل على الجانب غير الآمن (في العالم العادي) للاتصال بالخدمات التي يتم نشرها من خلال الجانب الآمن فقط.

وبناءً على المتطلبات، يمكن أن يكون التطبيق الموثوق به عميلاً الخادم في نفس الوقت. يشير هذا المصطلح إلى تطبيق موثوق به ينشر خدمة (ك الاتصال بخدمات أخرى (كعميل).

واجهة برمجة التطبيقات الاسم المعرِّف

الأسماء المعرِّفة هي أعداد صحيحة غير موقعة تمثل الموارد مثل المنافذ القنوات الشبيهة بأدوات وصف الملفات في نظام التشغيل UNIX. بعد إنشاء الأسماء المعرِّفة، يتم وضعها في جدول الأسماء المعرِّفة الخاصة بالتطبيق ويمكن الرجوع إليها لاحقًا.

يمكن للمتصل ربط البيانات الخاصة باسم معرِّف باستخدام طريقة set_cookie().

الطُرق المتوفّرة في واجهة برمجة التطبيقات Handle API

تكون الأسماء المعرِّفة صالحة فقط في سياق التطبيق. يجب أن يكون التطبيق عدم تمرير قيمة الاسم المعرِّف إلى تطبيقات أخرى ما لم يتم المحددة. يجب تفسير قيمة الاسم المعرِّف فقط من خلال مقارنتها الـ INVALID_IPC_HANDLE #define, الذي يمكن لأحد التطبيقات استخدامه إلى أنّ الاسم المعرِّف غير صالح أو بدون ضبط.

تربط البيانات الخاصة التي يقدّمها المتصل باسم معرِّف محدّد.

long set_cookie(uint32_t handle, void *cookie)

[in] handle: أي اسم معرِّف يعرضه أحد طلبات البيانات من واجهة برمجة التطبيقات

[in] cookie: مؤشر للبيانات العشوائية الخاصة بمساحة المستخدم في تطبيق Trusty

[retval]: NO_ERROR عند نجاح الإجراء، رمز خطأ < 0 في الحالات الأخرى

يفيد هذا الاستدعاء في التعامل مع الأحداث عند وقوعها في وقت لاحق بعد تم إنشاء الاسم المعرِّف. تعمل آلية التعامل مع الحدث على توفير المقبض وملف تعريف الارتباط الخاص به إلى معالج الأحداث.

يمكن انتظار الأسماء المعرِّفة للأحداث باستخدام المكالمة wait().

والانتظار()

وهي تنتظر وقوع حدث على اسم معرِّف معيَّن لفترة زمنية محدَّدة.

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

[in] handle_id: أي اسم معرِّف يعرضه أحد طلبات البيانات من واجهة برمجة التطبيقات

[out] event: مؤشر إلى البنية التي تمثل حدث على هذا الاسم المعرِّف

[in] timeout_msecs: قيمة المهلة بالمللي ثانية، CANNOT TRANSLATE قيمة -1 هي مهلة لانهائية

[استرجاع]: NO_ERROR في حال وقوع حدث صالح خلال الفاصل الزمني المحدد للمهلة ERR_TIMED_OUT في حال انقضاء مهلة محددة بدون انتهاء مهلة محددة وقع الحدث؛ < 0 بسبب الأخطاء الأخرى

عند النجاح (retval == NO_ERROR)، تم إجراء مكالمة wait(). يملأ بنية 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، الذي يتم محوه عند القراءة ويكون لدى التطبيق فرصة واحدة فقط للتعامل معها.

يمكن إلغاء الأسماء المعرِّفة من خلال استدعاء الطريقة close().

Close()

إتلاف المورد المرتبط بالاسم المعرِّف المحدد وإزالته من جدول المقبض.

long close(uint32_t handle_id);

[في] handle_id: الاسم المعرِّف للتدمير

[retval]: 0 في حالة النجاح؛ خطأ سلبي بخلاف ذلك

واجهة برمجة تطبيقات الخادم

يبدأ الخادم بإنشاء واحد أو أكثر من المنافذ المُعنونة التي تمثل نقاط نهاية خدمتها. يتم تمثيل كل منفذ بمقبض.

الطرق المتاحة في 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 يتم تخصيص مخزن مؤقت لإرسال واستلام مخزن مؤقت واحد بشكل مسبق. بشكل عام، يتزايد عدد الموارد الاحتياطية مطلوبة على اتفاقية البروتوكول ذات المستوى الأعلى بين العميل الخادم. يمكن أن يكون الرقم أقل من 1 في حال استخدام بروتوكول متزامن للغاية (إرسال رسالة وتلقّي رد قبل إرسال رسالة أخرى) لكن يمكن أن يكون العدد أكثر إذا كان العميل يتوقع إرسال أكثر من رسالة واحدة قبل أن يتمكن الرد (مثال: رسالة كمقدمة وأخرى كأمر فعلي). تشير رسالة الأشكال البيانية مجموعات الموارد الاحتياطية المخصصة لكل قناة، أي اتصالين منفصلين (قناتين) سيكون لها مجموعات موردة منفصلة.

[in] recv_buf_size: الحد الأقصى لحجم كل مورد احتياطي فردي في أعلى من المخزن المؤقت. هذه القيمة تعتمد على البروتوكول وتحدّ بشكل فعّال من الحد الأقصى لحجم الرسالة التي يمكنك تبادلها مع نظير

[in] flags: مجموعة من العلامات التي تحدد سلوك المنفذ الإضافي

يجب أن تكون هذه القيمة مجموعة من القيم التالية:

IPC_PORT_ALLOW_TA_CONNECT: يسمح هذا الخيار بالاتصال من تطبيقات آمنة أخرى.

IPC_PORT_ALLOW_NS_CONNECT - يسمح بالاتصال من عالم غير آمن

[استرداد]: التعامل مع المنفذ الذي تم إنشاؤه إذا كانت القيمة غير سالبة أو إذا كان هناك خطأ معيّن سلبي

ثم يستطلع الخادم قائمة الأسماء المعرِّفة للمنافذ للاتصالات الواردة. باستخدام مكالمة wait(). عند تلقّي اتصال الطلب المشار إليه بمجموعة بت IPC_HANDLE_POLL_READY في الحقل event في بنية uevent_t، يجب أن يتصل الخادم بـ accept() لإنهاء عملية إنشاء اتصال وإنشاء قناة (ممثلة اسم معرِّف آخر) يمكن بعد ذلك البحث عنها بحثًا عن الرسائل الواردة.

before()قبول

يقبل أي اتصال وارد ويحصل على اسم معرِّف للقناة.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id: الاسم المعرِّف الذي يمثل المنفذ الذي اتصل به العميل

[out] peer_uuid: الإشارة إلى بنية uuid_t المطلوب معبأ بالمعرّف الفريد العالمي (UUID) لتطبيق العميل المتصل. أُنشأها جون هنتر، الذي كان متخصصًا سيتم تعيينها على جميع الأصفار إذا نشأ الاتصال من العالم غير الآمن.

[استرداد]: التعامل مع قناة (إذا لم تكن سالبة) يمكن للخادم استخدامها تبادل الرسائل مع العميل (أو رمز خطأ في الحالات الأخرى)

واجهة برمجة تطبيقات العميل

يحتوي هذا القسم على الطرق الموجودة في Client API.

الطُرق في واجهة برمجة تطبيقات العميل

Connect()

لإجراء اتصال بمنفذ محدد حسب الاسم.

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

[في] path: اسم المنفذ الذي نشره أحد تطبيقات Trusty

[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 هي نفسه للخوادم والعملاء.

يتلقّى العميل اسمًا معرِّفًا لقناة من خلال إصدار connect(). ويحصل الخادم على اسم معرِّف للقناة من مكالمة accept()، الموضحة أعلاه.

بنية رسالة الالضمان

وكما هو موضح في ما يلي، تكون الرسائل التي تتبادلها واجهة برمجة التطبيقات 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);

[في] handle: الاسم المعرِّف للقناة التي سيتم إرسال الرسالة إليها

[في] 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);

[في] handle: الاسم المعرِّف للقناة التي يجب استرداد رسالة جديدة منها

[out] msg_info: بنية معلومات الرسالة موضّحة على النحو التالي:

typedef struct ipc_msg_info {
        size_t    len;  /* total message length */
        uint32_t  id;   /* message id */
} ipc_msg_info_t;

يتم تعيين مُعرّف فريد لكل رسالة عبر مجموعة الرسائل المعلّقة، ويتم ملء المدة الإجمالية لكل رسالة. في حال ضبط الإعدادات والسماح بها من قِبل فقد يكون هناك العديد من الرسائل المعلقة (المفتوحة) في وقت واحد قناة معينة.

[retval]: NO_ERROR عند النجاح؛ خطأ سلبي بخلاف ذلك

read_msg()

يقرأ محتوى الرسالة بالمعرِّف المحدد بدءًا من الإزاحة المحددة.

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

[في] handle: الاسم المعرِّف للقناة التي ستتم قراءة الرسالة منها

[في] msg_id: معرّف الرسالة المطلوب قراءتها

[في] offset: المسافة إلى الرسالة التي يتم بدء القراءة منها

[out] msg: مؤشر إلى بنية ipc_msg_t التي تصف مجموعة من الموارد الاحتياطية التي يمكن تخزين الرسالة الواردة فيها البيانات

[retval]: إجمالي عدد وحدات البايت المخزنة في مخازن msg المؤقتة على النجاح؛ خطأ سلبي بخلاف ذلك

يمكن استدعاء طريقة read_msg عدة مرات بدءًا من مختلفًا (ليس بالضرورة متسلسلة) تعويضها حسب الحاجة.

Place_msg()

إزالة رسالة ذات معرّف محدد.

long put_msg(uint32_t handle, uint32_t msg_id);

[في] handle: الاسم المعرِّف للقناة التي تم إرسال الرسالة إليها

[في] msg_id: معرّف الرسالة التي سيتم إزالتها

[retval]: NO_ERROR عند النجاح؛ خطأ سلبي بخلاف ذلك

لا يمكن الوصول إلى محتوى الرسالة بعد إنهاء العمل بالرسالة المخزن المؤقت الذي شغله وتم تحريره.

واجهة برمجة تطبيقات واصف الملفات

تشمل واجهة برمجة التطبيقات 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() هي no-ops ويجب أن تعرض الخطأ 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);

[في] fd: واصف الملف المطلوب القراءة منه

[out] buf: المؤشر إلى المخزن المؤقت الذي سيتم تخزين البيانات فيه

[in] count: الحد الأقصى لعدد وحدات البايت المراد قراءتها

[retval]: تم عرض عدد وحدات البايت المقروءة؛ خطأ سلبي بخلاف ذلك

كتابة()

تكتب ما يصل إلى count بايت من البيانات في واصف الملف المحدد.

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

[في] fd: واصف الملف المطلوب الكتابة إليه

[out] buf: مؤشر إلى البيانات للكتابة

[in] count: الحد الأقصى لعدد وحدات البايت المراد الكتابة

[retval]: تم عرض عدد وحدات البايت المكتوبة خطأ سلبي بخلاف ذلك

()ioctl

لاستدعاء أمر ioctl محدد لواصف ملف معين.

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

[في] fd: واصف الملف المطلوب استدعاء ioctl() عليه

[في] cmd: الأمر ioctl

[in/out] args: المؤشر على ioctl() وسيطة

واجهة برمجة تطبيقات متنوعة

الطرق في واجهة برمجة التطبيقات المتنوعة

gettime()

تعرض وقت النظام الحالي (بالنانو ثانية).

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

[in] clock_id: مستند إلى النظام الأساسي تجاوز القيمة التلقائية

[في] flags: محجوزة، ويجب أن تكون صفرًا

[out] time: المؤشر إلى قيمة int64_t لتخزين الوقت الحالي

[retval]: NO_ERROR عند النجاح؛ خطأ سلبي بخلاف ذلك

()nanosleep

تعليق تنفيذ تطبيق الاتصال لفترة زمنية محدَّدة ويستأنفه بعد تلك الفترة.

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

[في] clock_id: محجوزة، ويجب أن تكون صفرًا

[في] 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 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;
}

ترسل الطريقة run_end_to_end_msg_test() 10,000 رسالة بشكل غير متزامن. إلى "الصدى" الخدمة والأسماء المعرِّفة الردود.

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 وبرامج مساحة المستخدمين التي تعمل على الجانب غير الآمن.

بيئة التنفيذ على الجانب غير الآمن (النواة ومساحة المستخدم) تختلف اختلافًا كبيرًا عن بيئة التنفيذ من الناحية الآمنة. لذلك، بدلاً من وجود مكتبة واحدة لكلتا البيئتين، هناك اثنين مجموعات مختلفة من واجهات برمجة التطبيقات. في النواة kernel، يتم توفير واجهة برمجة تطبيقات العميل بواسطة برنامج تشغيل نواة ipc موثوق به ويسجّل عقدة جهاز الحرف التي يمكن استخدامها عمليات مساحة المستخدم للتواصل مع الخدمات قيد التشغيل على الجانبي.

واجهة برمجة تطبيقات عميل Trusty IPC لمساحة المستخدم

مكتبة 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 المحددة. تم تمرير جميع البيانات إلى مكالمة write() المذكورة أعلاه يتم تحويلها إلى رسالة بواسطة برنامج تشغيل ipc الموثوق. الرسالة هي إلى الجانب الآمن حيث يتم التعامل مع البيانات بواسطة النظام الفرعي IPC في نواة Trusty وتوجيهها إلى الوجهة المناسبة وتسليمها إلى أحد التطبيقات تكرار الأحداث كحدث IPC_HANDLE_POLL_MSG على قناة معيّنة الاسم المعرِّف. اعتمادًا على التفاصيل، الخاص بالخدمة، فقد ترسل خدمة Trusty ردًا واحدًا أو أكثر الرسائل التي يتم تسليمها مرة أخرى إلى الجانب غير الآمن ويتم وضعها في قائمة انتظار رسائل واصف ملف القناة المناسبة التي سيستردها المستخدم اتصال تطبيق المساحة read().

tipc_connect()

يؤدي هذا الاختصار إلى فتح عقدة جهاز tipc محدَّدة وبدء اتصال بخدمة Trusty محدَّدة.

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

[في] dev_name: المسار إلى عقدة جهاز Trusty IPC لفتحها

[في] srv_name: اسم خدمة Trusty المنشورة التي سيتم الربط بها

[retval]: واصف ملف صالح عند نجاح الإجراء، -1 بخلاف ذلك

tipc_Close()

لإغلاق الاتصال بخدمة Trusty المحدّدة من خلال واصف الملف.

int tipc_close(int fd);

[في] fd: أداة وصف الملف سبق أن تم فتحها بواسطة مكالمة tipc_connect()

واجهة برمجة تطبيقات عميل Kernel Trusty IPC

تتوفر واجهة برمجة تطبيقات العميل kernel Trusty IPC لبرامج تشغيل kernel. المستخدِم Space 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 (على الجانب الآمن)، والتي تم ربط القناة

بعد نجاح الوضع في قائمة الانتظار، من المفترض أن ينسى المتصل المخزن المؤقت للرسائل لأن المخزن المؤقت للرسائل يعود في النهاية إلى مجمع المخزن المؤقت المجاني بعد المعالجة عن بُعد (لإعادة استخدامها لاحقًا أو في رسائل أخرى). المستخدِم يحتاج فقط إلى طلب tipc_chan_put_txbuf() في حال تعذّر هذا المخزن المؤقت في قائمة انتظار أو أنه لم يعد مطلوبًا.

يتلقى مستخدم واجهة برمجة التطبيقات الرسائل من الجانب البعيد من خلال التعامل مع معاودة الاتصال بإشعار handle_msg() (يتم الاتصال خلال سياق قائمة انتظار العمل rx الموثوقة (ipc) الذي لتوفير مؤشر للمخزن المؤقت rx الذي يحتوي على معالجة الرسائل الواردة.

من المتوقّع تلقّي معاودة الاتصال "handle_msg()" سيؤدي التنفيذ إلى إرجاع المؤشر إلى struct tipc_msg_buf صالح. ويمكن أن يكون نفس الشيء مثل المخزن المؤقت للرسائل الواردة إذا تمت معالجته محليًا ولم تعد مطلوبة بعد الآن. وبدلاً من ذلك، يمكن أن يكون موردًا احتياطيًا جديدًا يتم الحصول عليه من مكالمة tipc_chan_get_rxbuf() إذا كان المخزن المؤقت الوارد في قائمة الانتظار لمزيد من المعالجة. يجب تتبُّع مخزن مؤقت منفصل rx وتم إصداره في النهاية باستخدام استدعاء tipc_chan_put_rxbuf() عندما لم تعد هناك حاجة إليها.

الطرق في واجهة برمجة تطبيقات العميل Kernel Trusty IPC

tipc_create_channel()

تنشئ وتضبط مثيلاً لقناة Trusty IPC لقناة معيّنة جهاز IPC موثوق به.

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

[in] dev: المؤشر إلى عنوان IP الموثوق به الذي تم استخدام الجهاز من أجله تمّ إنشاء قناة

[في] ops: مؤشر إلى struct tipc_chan_ops، مع خاص بالمتصل تم ملء بيانات معاودة الاتصال

[in] cb_arg: مؤشر للبيانات التي سيتم تمريرها إلى tipc_chan_ops طلبات معاودة الاتصال

[استرجاع]: مؤشر إلى مثيل تم إنشاؤه حديثًا struct tipc_chan على النجاح، ERR_PTR(err) في الحالات الأخرى

بشكل عام، يجب أن يقدم المتصل استدعاءين لم يتم استدعاؤهما بشكل غير متزامن عند حدوث النشاط المقابل.

تم استدعاء الحدث void (*handle_event)(void *cb_arg, int event) لإعلام المتّصل بشأن تغيير حالة القناة.

[in] cb_arg: مؤشر للبيانات التي تم تمريرها إلى مكالمة واحدة (tipc_create_channel())

[في] 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())
  • [في] 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);

[في] chan: الإشارة إلى القناة التي سيتم وضع رسالة عليها في قائمة المحتوى التالي

[in] chan: الحد الأقصى للمهلة للانتظار حتى يصبح المخزن المؤقت "tx" متاحًا

[retval]: مخزن مؤقت صالح للرسائل عند النجاح، ERR_PTR(err) على خطأ

tipc_chan_queue_msg()

وضع الرسالة في قائمة الانتظار لإرسالها عبر قنوات IPC موثوقة.

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

[في] chan: الإشارة إلى القناة التي سيتمّ وضع الرسالة عليها في قائمة الانتظار

[في] 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);

[في] chan: الإشارة إلى القناة التي ينتمي هذا المخزن المؤقت للرسائل

[in] mb: المؤشر على المخزن المؤقت للرسائل من أجل الإصدار

[retval]: لا شيء

tipc_chan_get_rxbuf()

الحصول على مخزن رسائل مؤقت جديد يمكن استخدامه لتلقي الرسائل عبر القناة المحددة.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[في] 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);

[في] chan: مؤشر إلى قناة ينتمي إليها المخزن المؤقت للرسائل

[in] mb: المؤشر على المخزن المؤقت للرسائل من أجل الإصدار

[retval]: لا شيء