مرجع API موثوق

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

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

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

الموانئ والقنوات

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

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

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

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

التعامل مع API

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

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

الأساليب في التعامل مع API

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

يربط البيانات الخاصة التي يوفرها المتصل بمعامل محدد.

long set_cookie(uint32_t handle, void *cookie)

handle [in]: أي مؤشر يتم إرجاعه بواسطة إحدى استدعاءات 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 [الخروج]: مؤشر إلى الهيكل الذي يمثل حدثًا وقع على هذا المقبض

[في] 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 : التعامل مع التدمير

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

API الخادم

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

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

port_create ()

ينشئ منفذ خدمة مسمى.

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

path [في]: اسم سلسلة المنفذ (كما هو موضح أعلاه). يجب أن يكون هذا الاسم فريدًا عبر النظام ؛ ستفشل محاولات إنشاء نسخة مكررة.

[في] num_recv_bufs : الحد الأقصى لعدد المخازن المؤقتة التي يمكن لقناة على هذا المنفذ تخصيصها مسبقًا لتسهيل تبادل البيانات مع العميل. يتم حساب المخازن المؤقتة بشكل منفصل للبيانات التي تسير في كلا الاتجاهين ، لذا فإن تحديد 1 هنا يعني إرسال 1 ومخزن استقبال واحد يتم تخصيصه مسبقًا. بشكل عام ، يعتمد عدد المخازن المؤقتة المطلوبة على اتفاقية بروتوكول المستوى الأعلى بين العميل والخادم. يمكن أن يكون الرقم أقل من 1 في حالة وجود بروتوكول متزامن للغاية (إرسال رسالة ، تلقي الرد قبل إرسال آخر). ولكن يمكن أن يكون الرقم أكثر إذا كان العميل يتوقع إرسال أكثر من رسالة قبل ظهور الرد (على سبيل المثال ، رسالة كمقدمة وأخرى كأمر فعلي). تكون مجموعات المخزن المؤقت المخصصة لكل قناة ، لذلك سيكون لتوصيلتين منفصلتين (قناتين) مجموعات مخزن مؤقت منفصلة.

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

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

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

IPC_PORT_ALLOW_TA_CONNECT - يسمح بالاتصال من تطبيقات آمنة أخرى

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

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

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

قبول()

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

long accept(uint32_t handle_id, uuid_t *peer_uuid);

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

[out] peer_uuid : مؤشر إلى بنية uuud_t ليتم ملؤه بالمعرف الفريد العمومي (UUID) لتطبيق العميل المتصل. سيتم تعيينه على جميع الأصفار إذا نشأ الاتصال من العالم غير الآمن

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

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

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

الطرق في Client API

الاتصال()

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

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

path [في]: اسم المنفذ المنشور بواسطة تطبيق موثوق به

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 قبل بدء التشغيل العادي.

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

تتيح مكالمات واجهة برمجة تطبيقات المراسلة إرسال الرسائل وقراءتها عبر اتصال (قناة) تم إنشاؤه مسبقًا. استدعاءات واجهة برمجة تطبيقات المراسلة هي نفسها للخوادم والعملاء.

يتلقى العميل مؤشرًا للقناة عن طريق إصدار 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);

مقبض [in]: 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 [in]: معالجة القناة التي يجب استرداد رسالة جديدة عليها

[إخراج] 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 [in]: معالجة القناة التي تقرأ منها الرسالة

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

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

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

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

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

put_msg ()

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

long put_msg(uint32_t handle, uint32_t msg_id);

handle [in]: معالجة القناة التي وصلت الرسالة إليها

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

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

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

ملف واصف API

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

بشكل افتراضي ، هناك 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 (لتنفيذ امتدادات خاصة بالمنصة) ، فإن توسيع واصفات الملفات يحتاج إلى توخي الحذر. يؤدي توسيع واصفات الملفات إلى إنشاء تعارضات ولا يوصى به بشكل عام.

الأساليب في File Descriptor API

قرأ()

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

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

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

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

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

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

API المتنوعة

الطرق في متفرقات API

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

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

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

[في] clock_id : يعتمد على المنصة ؛ تمرير الصفر للتقصير

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

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

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

نانوسليب ()

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

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

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

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

[في] وقت النوم: وقت النوم 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() يرسل 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 بواسطة برنامج تشغيل Trusty-ipc kernel ويسجل عقدة جهاز شخصية يمكن استخدامها بواسطة عمليات مساحة المستخدم للتواصل مع الخدمات التي تعمل على الجانب الآمن.

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

تعد مكتبة 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() أعلاه إلى رسالة بواسطة برنامج Trusty-ipc. يتم تسليم الرسالة إلى الجانب الآمن حيث يتم معالجة البيانات بواسطة النظام الفرعي IPC في Trusty kernel وتوجيهها إلى الوجهة المناسبة وتسليمها إلى حلقة حدث التطبيق كحدث 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 : اسم خدمة موثوقة منشورة للاتصال بها

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

tipc_close ()

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

int tipc_close(int fd);

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

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

يتوفر kernel Trusty IPC Client API لبرامج تشغيل kernel. يتم تنفيذ 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() (والذي يسمى في سياق مجموعة عمل 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 لجهاز 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()

[في] 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()
  • [في] 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 [في]: مؤشر لسلسلة تحتوي على اسم الخدمة المراد الاتصال بها

[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 ()

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

[في] 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 : مؤشر إلى القناة التي ينتمي إليها المخزن المؤقت للرسالة

[في] 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 : مؤشر إلى القناة التي ينتمي إليها المخزن المؤقت للرسالة

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

[retval]: لا شيء