مرجع API موثوق

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

  • التطبيقات أو الخدمات الموثوقة التي تعمل على معالج TEE
  • التطبيقات العادية/غير الموثوقة التي تعمل على المعالج الرئيسي وتستخدم الخدمات التي تقدمها التطبيقات الموثوقة

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

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

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

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

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

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

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

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

يمكن للمتصل ربط البيانات الخاصة بمقبض باستخدام الأسلوب set_cookie() .

الأساليب في Handle API

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

يربط البيانات الخاصة المقدمة من المتصل بمقبض محدد.

long set_cookie(uint32_t handle, void *cookie)

[في] handle : أي مقبض يتم إرجاعه بواسطة إحدى استدعاءات واجهة برمجة التطبيقات (API).

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

[retval]: NO_ERROR عند النجاح، < 0 رمز الخطأ بخلاف ذلك

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

يمكن انتظار المقابض للأحداث باستخدام استدعاء wait() .

انتظر()

ينتظر وقوع حدث على مؤشر معين لفترة زمنية محددة.

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

[في] handle_id : أي مؤشر يتم إرجاعه بواسطة إحدى استدعاءات واجهة برمجة التطبيقات (API).

event [out]: مؤشر إلى البنية التي تمثل حدثًا وقع على هذا المقبض

[في] timeout_msecs : قيمة المهلة بالمللي ثانية؛ قيمة -1 هي مهلة لا نهائية

[retval]: 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() .

يغلق()

يدمر المورد المرتبط بالمقبض المحدد ويزيله من جدول المقبض.

long close(uint32_t handle_id);

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

[إعادة التقييم]: 0 في حالة النجاح؛ خطأ سلبي خلاف ذلك

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

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

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

port_create()

إنشاء منفذ خدمة مسمى.

long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size,
uint32_t flags)

[في] 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() لإنهاء إنشاء اتصال وإنشاء قناة (ممثلة بمقبض آخر) يمكن بعد ذلك استقصاؤها للرسائل الواردة .

يقبل()

يقبل اتصالاً واردًا ويحصل على مؤشر للقناة.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[في] handle_id : مقبض يمثل المنفذ الذي يتصل به العميل

[خارج] peer_uuid : مؤشر إلى بنية uuid_t المراد ملؤها بـ UUID الخاص بتطبيق العميل المتصل. سيتم ضبطه على كافة الأصفار إذا نشأ الاتصال من عالم غير آمن

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

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

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

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

يتصل()

يبدأ الاتصال بمنفذ محدد بالاسم.

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 إرسال الرسائل وقراءتها عبر اتصال (قناة) تم إنشاؤه مسبقًا. مكالمات 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 هو عشوائي تمامًا.

الأساليب الموجودة في واجهة برمجة التطبيقات للمراسلة

send_msg()

يرسل رسالة عبر قناة محددة.

long send_msg(uint32_t handle, ipc_msg_t *msg);

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

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

[خارج] msg_info : تم وصف بنية معلومات الرسالة على النحو التالي:

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

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

[إرجاع]: 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 عدة مرات بدءًا من إزاحة مختلفة (ليست بالضرورة تسلسلية) حسب الحاجة.

put_msg()

يتقاعد رسالة بمعرف محدد.

long put_msg(uint32_t handle, uint32_t msg_id);

[في] handle : مقبض القناة التي وصلت الرسالة عليها

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

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

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

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

تتضمن واجهة برمجة تطبيقات File Descriptor استدعاءات read() و write() و ioctl() . يمكن لجميع هذه الاستدعاءات أن تعمل على مجموعة محددة مسبقًا (ثابتة) من واصفات الملفات التي يتم تمثيلها تقليديًا بأرقام صغيرة. في التطبيق الحالي، تكون مساحة واصف الملف منفصلة عن مساحة مؤشر IPC. تشبه واجهة File Descriptor API في Trusty واجهة برمجة التطبيقات التقليدية القائمة على واصف الملف.

افتراضيًا، هناك 3 واصفات ملفات محددة مسبقًا (قياسية ومعروفة):

  • 0 - الإدخال القياسي. التنفيذ الافتراضي للإدخال القياسي fd هو no-op (حيث لا يُتوقع أن تحتوي التطبيقات الموثوقة على وحدة تحكم تفاعلية) لذا فإن قراءة أو كتابة أو استدعاء ioctl() على fd 0 يجب أن يُرجع خطأ ERR_NOT_SUPPORTED .
  • 1 - الإخراج القياسي. يمكن توجيه البيانات المكتوبة إلى الإخراج القياسي (اعتمادًا على مستوى تصحيح LK) إلى UART و/أو سجل الذاكرة المتوفر على الجانب غير الآمن، اعتمادًا على النظام الأساسي والتكوين. يجب أن يتم إدخال سجلات ورسائل تصحيح الأخطاء غير المهمة في الإخراج القياسي. الطريقتان read() و ioctl() لا تحتاجان إلى عمليات ويجب أن تُرجعا الخطأ ERR_NOT_SUPPORTED .
  • 2- الخطأ المعياري. يجب توجيه البيانات المكتوبة على أنها خطأ قياسي إلى UART أو سجل الذاكرة المتوفر على الجانب غير الآمن، اعتمادًا على النظام الأساسي والتكوين. يوصى بكتابة الرسائل الهامة فقط للخطأ القياسي، حيث من المحتمل جدًا أن يتم عدم تقييد هذا الدفق. الطريقتان read() و ioctl() لا تحتاجان إلى عمليات ويجب أن تُرجعا الخطأ ERR_NOT_SUPPORTED .

على الرغم من أنه يمكن توسيع هذه المجموعة من واصفات الملفات لتنفيذ المزيد من fds (لتنفيذ ملحقات خاصة بالنظام الأساسي)، إلا أن توسيع واصفات الملفات يحتاج إلى ممارسة بحذر. قد يؤدي توسيع واصفات الملفات إلى إنشاء تعارضات ولا يوصى به بشكل عام.

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

يقرأ()

يحاول قراءة ما يصل إلى count بايت من البيانات من واصف ملف محدد.

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

[in] fd : واصف الملف الذي يمكن القراءة منه

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

[في] count : الحد الأقصى لعدد البايتات للقراءة

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

يكتب()

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

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

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

[خارج] buf : مؤشر إلى البيانات المراد كتابتها

[في] count : الحد الأقصى لعدد البايتات المراد كتابتها

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

إيوكتل ()

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

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

[in] fd : واصف الملف الذي سيتم من خلاله استدعاء ioctl()

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

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

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

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

احصل على وقت()

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

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

[في] clock_id : يعتمد على النظام الأساسي؛ تمرير الصفر الافتراضي

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

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

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

نانو سليب()

إيقاف تنفيذ طلب الاستدعاء لفترة زمنية محددة واستئنافه بعد تلك الفترة.

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

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

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

[in] sleep_time : وقت النوم بالنانو ثانية

[إرجاع]: 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() 10000 رسالة بشكل غير متزامن إلى خدمة "الصدى" وتتعامل مع الردود.

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

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

مجموعة من الخدمات الموثوقة، المنشورة من الجانب الآمن والمميزة بالخاصية IPC_PORT_ALLOW_NS_CONNECT ، يمكن الوصول إليها من قبل برامج kernel ومساحة المستخدم التي تعمل على الجانب غير الآمن.

تختلف بيئة التنفيذ على الجانب غير الآمن (النواة ومساحة المستخدم) بشكل كبير عن بيئة التنفيذ على الجانب الآمن. ولذلك، بدلاً من مكتبة واحدة لكلا البيئتين، هناك مجموعتان مختلفتان من واجهات برمجة التطبيقات. في kernel، يتم توفير Client API بواسطة برنامج تشغيل kernel Trusty-IPC ويسجل عقدة جهاز الأحرف التي يمكن استخدامها بواسطة عمليات مساحة المستخدم للتواصل مع الخدمات التي تعمل على الجانب الآمن.

مساحة المستخدم Trusty IPC Client API

تعد مكتبة Trusty IPC Client API لمساحة المستخدم عبارة عن طبقة رقيقة أعلى عقدة الجهاز fd .

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

Tipc_connect()

يفتح عقدة جهاز tipc محددة ويبدأ الاتصال بخدمة موثوقة محددة.

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

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

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

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

Tipc_Close()

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

int tipc_close(int fd);

[in] fd : واصف الملف الذي تم فتحه مسبقًا بواسطة استدعاء tipc_connect()

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

تتوفر واجهة برمجة تطبيقات kernel Trusty IPC Client لبرامج تشغيل kernel. يتم تنفيذ واجهة برمجة التطبيقات Trusty IPC API لمساحة المستخدم أعلى واجهة برمجة التطبيقات هذه.

بشكل عام، الاستخدام النموذجي لواجهة برمجة التطبيقات (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() للتسليم إلى خدمة موثوقة (على الجانب الآمن)، والتي تتصل بها القناة

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

يتلقى مستخدم واجهة برمجة التطبيقات (API) رسائل من الجانب البعيد عن طريق معالجة رد اتصال إشعار handle_msg() (والذي يتم استدعاؤه في سياق قائمة انتظار عمل 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 : مؤشر إلى ipc الموثوق به الذي تم إنشاء قناة الجهاز من أجله

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

[في] cb_arg : مؤشر إلى البيانات التي سيتم تمريرها إلى عمليات الاسترجاعات tipc_chan_ops

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

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

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

[في] 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) لتقديم إشعار باستلام رسالة جديدة عبر قناة محددة:

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

[في] chan : مؤشر إلى قناة تم إرجاعها بواسطة استدعاء tipc_create_chan()

port [in]: مؤشر إلى سلسلة تحتوي على اسم الخدمة المراد الاتصال بها

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

يتم إخطار المتصل عند إنشاء اتصال عن طريق تلقي رد اتصال handle_event .

Tipc_chan_shutdown()

ينهي الاتصال بخدمة Trusty IPC التي تم إنشاؤها مسبقًا بواسطة استدعاء tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[في] chan : مؤشر إلى قناة تم إرجاعها بواسطة استدعاء tipc_create_chan()

Tipc_chan_destroy()

يدمر قناة IPC الموثوقة المحددة.

void tipc_chan_destroy(struct tipc_chan *chan);

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

يقوم بوضع رسالة ليتم إرسالها عبر قنوات Trusty 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 : مؤشر إلى المخزن المؤقت للرسائل المراد تحريره

[إعادة التدوير]: لا يوجد

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 : مؤشر إلى المخزن المؤقت للرسائل المراد تحريره

[إعادة التدوير]: لا يوجد