חומר עזר של Trusty API

Trusty מספקת ממשקי API לפיתוח שני סוגים של אפליקציות/שירותים:

  • אפליקציות או שירותים מהימנים שפועלים במעבד TEE
  • אפליקציות רגילות/מהימנות שפועלות על המעבד הראשי ומשתמשות בשירותים שמסופקים באמצעות אפליקציות מהימנות

ה-Trust API מתאר באופן כללי את המערכת של Trusty בין תהליכים (IPC), כולל תקשורת עם העולם הלא מאובטח. תוכנה שפועלת על המעבד הראשי יכול להשתמש ב-Trusty APIs כדי להתחבר לאפליקציות/שירותים מהימנים ולהחליף איתם הודעות שרירותיות בדיוק כמו שירות רשת ב-IP. האפליקציה קובעת את פורמט הנתונים ואת הסמנטיקה של הודעות באמצעות פרוטוקול ברמת האפליקציה. מסירת הודעות אמינה מובטחת על ידי התשתית של Trusty (בתור מנהלי התקנים פועל במעבד הראשי), והתקשורת היא אסינכרונית לחלוטין.

יציאות וערוצים

היציאות משמשות את האפליקציות של Trusty כדי לחשוף נקודות קצה של שירות בטופס של נתיב בעל שם שאליו הלקוחות מתחברים. כך מתקבלת מודל מחרוזות פשוט מזהי שירות שבהם הלקוחות יכולים להשתמש. המוסכמה למתן שמות היא בסגנון DNS הפוך מתן שמות, למשל com.google.servicename

כשלקוח מתחבר ליציאה, הלקוח מקבל ערוץ לאינטראקציה באמצעות שירות. השירות חייב לקבל חיבור נכנס ובמועד כן, גם הוא מקבל ערוץ. בגדול, היציאות משמשות לחיפוש שירותים ואז התקשורת מתרחשת בשני ערוצים מחוברים (כלומר במופעים של החיבור לרשת אחרת). כשלקוח מתחבר ליציאה, מצב סימטרי נוצר חיבור דו-כיווני. באמצעות נתיב דו-צדדי מלא זה, לקוחות ושרתים יכולים להחליף הודעות שרירותיות עד שאחד מהצדדים מחליט לקרוע לאורך החיבור.

אפשר ליצור רק אפליקציות מהימנות בצד האבטחה או מודולי ליבה של Trusty יציאות. אפליקציות שפועלות בצד לא מאובטח (בעולם הרגיל) יכולות להתחבר רק לשירותים שפורסמו בצד המאובטח.

בהתאם לדרישות, אפליקציה מהימנה יכולה להיות גם לקוח וגם בו-זמנית. אפליקציה מהימנה שמפרסמת שירות (כ שרת) אולי יצטרך להתחבר לשירותים אחרים (כלקוח).

ממשק API של ידית

כינויים הם מספרים שלמים לא חתומים שמייצגים משאבים כמו יציאות בדומה למתארי קבצים ב-UNIX. אחרי שיוצרים את הכינויים, מוצבות בטבלת כינויים ספציפית לאפליקציה ואפשר להפנות אליהם מאוחר יותר.

מתקשר יכול לשייך מידע פרטי לכינוי באמצעות השיטה set_cookie().

שיטות ב-handle API

כינויים תקפים רק בהקשר של בקשה. האפליקציה צריכה לא להעביר את הערך של כינוי לאפליקציות אחרות, אלא אם כן במפורש שצוין. צריך לפרש את ערך הכינוי רק על ידי השוואה אליו INVALID_IPC_HANDLE #define, שבו אפליקציה יכולה להשתמש אינדיקציה לכך שכינוי מסוים לא תקין או לא מוגדר.

משייך את המידע הפרטי שסופק על ידי המתקשר/ת לכינוי שצוין.

long set_cookie(uint32_t handle, void *cookie)

[in] handle: כל כינוי שהוחזר על ידי אחת מהקריאות ל-API

[in] cookie: מצביע על נתונים שרירותיים של מרחב משתמשים באפליקציה Trusty

[retval]: NO_ERROR בפעולה בהצלחה, קוד שגיאה < 0 אחרת

השיחה הזו שימושית לטיפול באירועים כשהם מתרחשים במועד מאוחר יותר לאחר הכינוי נוצר. המנגנון לטיפול באירועים מספק את הכינוי ואת קובץ ה-cookie שלו חזרה לגורם המטפל באירועים.

אפשר להשתמש בהתקשרות wait() כדי לחכות כינויים לאירועים.

pending()

בהמתנה עד שאירוע יתרחש בכינוי מסוים בפרק זמן מוגדר.

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

[in] handle_id: כל כינוי שהוחזר על ידי אחת מהקריאות ל-API

[out] event: מצביע למבנה שמייצג אירוע שהתרחש בכינוי הזה

[in] timeout_msecs: ערך של זמן קצוב לתפוגה באלפיות השנייה; A הערך 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, נמחקת לאחר קריאה ולאפליקציה יש הזדמנות אחת בלבד לטפל בזה.

אפשר להשמיד כינויים באמצעות קריאה ל-method close().

Close()

המערכת מכבה את המשאב המשויך לכינוי שצוין ומסירה אותו מ: טבלת הכינוי.

long close(uint32_t handle_id);

[in] handle_id: ידית כדי להרוס

[retval]: 0 אם success; שגיאה שלילית, אחרת

ממשק API של שרת

שרת מתחיל ביצירת יציאה בעלת שם אחת או יותר שמייצגת נקודות הקצה שלו לשירות. כל יציאה מיוצגת על ידי נקודת אחיזה.

methods ב-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() כדי לסיים את יצירת החיבור וליצור ערוץ (המיוצג על ידי כינוי אחר) כך שניתן יהיה לבדוק אם יש בו הודעות נכנסות.

get()

מקבל קישור נכנס ומקבלים כינוי לערוץ.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id: הכינוי שמייצג את היציאה שאליה הלקוח התחבר

[out] peer_uuid: מצביע למבנה uuid_t מלאים במזהה הייחודי (UUID) של אפליקציית הלקוח המקשרת. הוא הערך יהיה כל האפסים אם מקור החיבור הוא בעולם לא מאובטח

[retval]: כינוי לערוץ (אם הוא לא שלילי) שבו השרת יכול להחליף הודעות עם הלקוח (או עם קוד שגיאה, אחרת)

ממשק API של לקוח

המקטע הזה מכיל את השיטות ב-Client API.

methods ב-Client API

Connect()

מתחיל חיבור ליציאה שצוינה בשם.

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

[in] 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 לפני שמתחילים פעולה רגילה.

ממשק API להעברת הודעות

הקריאות ל-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 הוא שרירותי לחלוטין.

שיטות ב-Messaging API

send_msg()

שליחת הודעה בערוץ שצוין.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle: כינוי לערוץ שאליו תישלח ההודעה

[in] msg: מצביע על ipc_msg_t structure שמתאר את ההודעה

[retval]: המספר הכולל של הבייטים שנשלחו בהצלחה; שגיאה שלילית, אחרת

אם הלקוח (או השרת) מנסה לשלוח הודעה בערוץ ו אין מקום בתור של הודעות היעד להשוואה, הערוץ עשוי מזינים מצב של חסימה לשליחה (זה אף פעם לא אמור לקרות במקרה סינכרוני פשוט פרוטוקול בקשה/תשובה, אבל עשוי לקרות במקרים מורכבים יותר) מצוין על-ידי החזרת קוד השגיאה ERR_NOT_ENOUGH_BUFFER. במקרה כזה, המתקשר צריך להמתין עד שהעמית ישחרר חלק על ידי אחזור הטיפול בהודעות והפסקת הטיפול בהן. מצוין באמצעות הביט IPC_HANDLE_POLL_SEND_UNBLOCKED שהוגדר השדה event במבנה uevent_t הוחזרה בעקבות הקריאה אל wait().

get_msg()

קבלת מטא-מידע על ההודעה הבאה בתור של הודעות נכנסות

של ערוץ ספציפי.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle: הכינוי של הערוץ שבו צריך לאחזר הודעה חדשה

[out] msg_info: מבנה פרטי ההודעה מתואר כך:

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

לכל הודעה מוקצה מזהה ייחודי מתוך קבוצת ההודעות הממתינות, והאורך הכולל של כל הודעה מתמלא. אם הוא מוגדר ומורשה על ידי יכולות להיות מספר הודעות ממתינות (פתוחות) בו-זמנית ערוץ ספציפי.

[retval]: NO_ERROR על הצלחה; שגיאה שלילית, אחרת

read_msg()

קריאת תוכן ההודעה עם המזהה שצוין החל מ- ההיסט שצוין.

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

[in] handle: הכינוי של הערוץ שממנו צריך לקרוא את ההודעה

[in] msg_id: המזהה של ההודעה שצריך לקרוא

[in] offset: מדורגת להודעה שממנה אפשר להתחיל לקרוא

[מחוץ] msg: מצביע למבנה ipc_msg_t שמתאר קבוצה של מאגרי נתונים זמניים לשמירת הודעות נכנסות רוחב פס

[retval]: המספר הכולל של הבייטים שמאוחסנים במאגרי הנתונים הזמניים של msg ב- הצלחה; שגיאה שלילית, אחרת

אפשר לקרוא לשיטה read_msg כמה פעמים החל מ- אחרת (לא בהכרח ברצף).

pull_msg()

מבטל הודעה עם מזהה שצוין.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle: הכינוי של הערוץ שאליו ההודעה הגיעה

[in] msg_id: המזהה של ההודעה שהוצאה משימוש

[retval]: NO_ERROR על הצלחה; שגיאה שלילית, אחרת

אי אפשר לגשת לתוכן ההודעה אחרי שהודעה הוצאה משימוש. מאגר הנתונים הזמני התפנה.

File Descriptor API

ה-File Descriptor API כולל את read(), write(), ו-ioctl() שיחות. כל הקריאות האלו יכולות לפעול על מערכת קבצים מוגדרת מראש (סטטית) ומתארים שבדרך כלל מיוצגים במספרים קטנים. כרגע, בהטמעה, המרחב של מתאר הקובץ נפרד מכינוי ה-IPC המרחב המשותף. ה-File Descriptor API ב-Trusty הוא בדומה ל-API מסורתי שמבוסס על מתאר קבצים.

כברירת מחדל, יש 3 מתארי קבצים מוגדרים מראש (רגילים וידועים):

  • 0 – קלט סטנדרטי. הטמעת ברירת המחדל של הקלט הרגיל fd היא לא-תפעול (מכיוון שלאפליקציות מהימנות לא צריך להיות מסוף) לכן קריאה, כתיבה או הפעלה של ioctl() ב-fd 0 אמורה להחזיר שגיאת ERR_NOT_SUPPORTED.
  • 1 – פלט סטנדרטי. ניתן לנתב נתונים שנכתבו לפלט הסטנדרטי (בהתאם ברמת ניפוי הבאגים LK) ל-UART ו/או יומן זיכרון זמין בצד, בהתאם לפלטפורמה ולתצורה. יומני ניפוי באגים לא קריטיים וגם ההודעות צריכות להופיע בפלט רגיל. read() וגם ioctl() ה-methods הן ללא תפעול ואמורה להחזיר שגיאת ERR_NOT_SUPPORTED.
  • 2 - שגיאת תקן. נתונים שנכתבו לשגיאה רגילה צריכים לנתב אל UART או יומן זיכרון הזמינים בצד הלא מאובטח, בהתאם לפלטפורמה ול הגדרה אישית. מומלץ לכתוב רק הודעות קריטיות לתוכן כי יש סבירות גבוהה שהסטרימינג הזה לא יהיה ויסות נתונים. read() וגם השיטות של ioctl() הן ללא תפעול וצריכות להחזיר שגיאת ERR_NOT_SUPPORTED.

למרות שאפשר להרחיב את קבוצת תיאורי הקבצים הזו כדי להטמיע עוד fds (כדי להטמיע סיומות ספציפיות לפלטפורמה), צריך להרחיב את תיאורי הקבצים יש לפעול בזהירות. לרוב, אפשר ליצור מתארי קבצים מורחבים סותרות, ולא מומלץ בדרך כלל.

שיטות ב-File Descriptor API

read()

ניסיונות לקרוא עד count בייטים של נתונים ממתאר קובץ שצוין.

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

[in] fd: מתאר קובץ שממנו צריך לקרוא

[out] buf: מצביע למאגר נתונים זמני שאליו יאוחסנו נתונים

[in] count: מספר הבייטים המקסימלי לקריאה

[retval]: הוחזר מספר הבייטים שנקראו; שגיאה שלילית, אחרת

Write()

כתיבה של עד count בייטים של נתונים למתאר קובץ שצוין.

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

[in] fd: מתאר קובץ שאליו צריך לכתוב

[out] buf: מצביע לנתונים לכתיבה

[in] count: מספר הבייטים המקסימלי לכתיבה

[retval]: מספר הבייטים שהוחזרו שנכתבו; שגיאה שלילית, אחרת

ioctl()

הפעלה של פקודת ioctl שצוינה למתאר קובץ נתון.

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

[in] fd: מתאר קובץ שבו צריך להפעיל את ioctl()

[in] cmd: הפקודה ioctl

[in/out] args: מצביע על ioctl() ארגומנטים

ממשקי API שונים

שיטות ב-Discellaneous API

gettime()

מחזירה את זמן המערכת הנוכחי (בננו-שניות).

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

[in] clock_id: תלוי-פלטפורמה; מצב אפס לברירת המחדל

[in] flags: שמור, צריך להיות אפס

[out] time: מצביע על ערך של int64_t שאליו צריך לשמור את השעה הנוכחית

[retval]: NO_ERROR על הצלחה; שגיאה שלילית, אחרת

nanosleep()

השעיית הביצוע של אפליקציית הקריאה לפרק זמן מסוים וממשיך אותה אחרי התקופה הזו.

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

[in] clock_id: שמור, צריך להיות אפס

[in] flags: שמור, צריך להיות אפס

[in] sleep_time: זמן השינה בננו-שניות

[retval]: NO_ERROR על הצלחה; שגיאה שלילית, אחרת

דוגמה לשרת אפליקציות מהימן

האפליקציה לדוגמה הבאה מציגה את השימוש בממשקי ה-API שצוינו למעלה. הדוגמה יוצרת 'הד' שירות שמטפל בחיבורים נכנסים מרובים משקף בחזרה למתקשר את כל ההודעות שהוא קיבל מלקוחות שנשלחו מהצד המאובטח או מהצד הלא מאובטח.

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

אפליקציות וממשקי API לא מאובטחים בעולם

קבוצה של שירותי Trusty, שפורסמו מהצד המאובטח ומסומנים באמצעות המאפיין IPC_PORT_ALLOW_NS_CONNECT, נגיש לליבה ותוכניות חלל של משתמשים שפועלות מהצד הלא מאובטח.

סביבת הביצוע בצד הלא מאובטח (ליבה ומרחב משתמש) היא שונה באופן משמעותי מסביבת הביצוע בצד המאובטח. לכן, במקום ספרייה אחת לשתי הסביבות, יש קבוצות שונות של ממשקי API. בליבה (kernel), ה-Client API מסופק על ידי מנהל התקן ליבה מהימן-ipc ורושם צומת של מכשיר תווים שבו אפשר להשתמש על ידי תהליכים במרחב המשתמש כדי לתקשר עם שירותים שפועלים צד שלישי.

מרחב המשתמשים 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() שצוינה למעלה הופך להודעה על ידי מנהל התקן ה-IPC המהימן. ההודעה היא מועברות לצד המאובטח שבו הנתונים עוברים עיבוד על ידי מערכת המשנה של IPC הליבה של Trusty שמנותבת ליעד המתאים ונשלחת לאפליקציה לולאת אירוע כאירוע IPC_HANDLE_POLL_MSG בערוץ מסוים כינוי. בהתאם לישות הספציפית, פרוטוקול ספציפי לשירות, השירות Trusty עשוי לשלוח תשובה אחת או יותר הודעות שנמסרות חזרה לצד הלא מאובטח וממוקמות ב תור ההודעות של מתאר קובץ הערוץ המתאים לאחזור על ידי המשתמש אפליקציית המרחב המשותף read().

tipc_connect()

פותח צומת מכשיר tipc ספציפי ומפעיל לשירות Trusty שצוין.

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

[in] dev_name: נתיב לפתיחה של מכשיר Trusty IPC

[in] srv_name: השם של שירות Trusty שפורסם שאליו צריך להתחבר

[retval]: מתאר קובץ תקף אחרי הצלחה, -1 אחרת.

Tipc_Close()

הלחצן סוגר את החיבור לשירות Trusty שצוין באמצעות מתאר קובץ.

int tipc_close(int fd);

[in] fd: מתאר הקובץ נפתח בעבר על ידי שיחת tipc_connect()

ממשק API של לקוח IPC ל-Kernel Trusty

ממשק ה-API של לקוח Trusty IPC של הליבה זמין למנהלי התקנים של ליבה. המשתמש/ת Space Trusty IPC API מוטמע בנוסף ל-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() למסירה לשירות Trusty (בצד המאובטח), שאליו הערוץ מחובר

אחרי שההעברה לרשימת 'הבאים בתור' תושלם בהצלחה, המתקשר צריך לשכוח את מאגר הנתונים הזמני של ההודעות כי מאגר הנתונים הזמני חוזר בסופו של דבר למאגר הנתונים הזמני מתבצע עיבוד בצד המרוחק (לשימוש חוזר מאוחר יותר, עבור הודעות אחרות). המשתמש/ת צריך לקרוא ל-tipc_chan_put_txbuf() רק אם הפעולה לא מצליחה בתור מאגר כזה, או שהוא כבר לא נדרש.

משתמש API מקבל הודעות מהצד המרוחק על ידי טיפול קריאה חוזרת של התראה אחת (handle_msg()) (התקשרות בעוד ההקשר של תור העבודה המהימן של rx IPC) מספק מצביע למאגר נתונים זמני של 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, עם נתונים ספציפיים מולאו קריאה חוזרת

[in] cb_arg: מצביע על הנתונים שיועברו אל tipc_chan_ops קריאות חוזרות

[retval]: מצביע למופע חדש של struct tipc_chan להצלחה, אחרת ERR_PTR(err)

באופן כללי, מתקשרים צריכים לספק שתי קריאות חוזרות (callback) שמופעלות באופן אסינכרוני. כאשר הפעילות המתאימה מתרחשת.

האירוע void (*handle_event)(void *cb_arg, int event) מופעל כדי להודיע למתקשר על שינוי במצב של הערוץ.

[in] cb_arg: המצביע לנתונים שמועברים שיחה ב-tipc_create_channel()

[in] event: אירוע שיכול להיות אחד מהערכים הבאים:

  • TIPC_CHANNEL_CONNECTED - מציין חיבור מוצלח לצד המרוחק
  • TIPC_CHANNEL_DISCONNECTED - מציין שהצד המרוחק דחה את נשלחה בקשת חיבור חדשה או שנשלחה בקשה ניתוק של הערוץ שחובר בעבר
  • TIPC_CHANNEL_SHUTDOWN - מציין שהצד המרוחק נכבה, סגירת כל החיבורים באופן סופי

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) קריאה חוזרת (callback) מופעלת כדי לספק התראה על קבלת הודעה חדשה התקבל בערוץ מסוים:

  • [in] cb_arg: המצביע לנתונים שהועברו אל שיחה ב-tipc_create_channel()
  • [in] mb: מצביע אל struct tipc_msg_buf תיאור של הודעה נכנסת
  • [retval]: הטמעת הקריאה החוזרת צפויה להחזיר מצביע struct tipc_msg_buf יכול להיות אותו מצביע שהתקבל בתור mb אם ההודעה מטופלת באופן מקומי ולא נדרש יותר (או יכול להיות מאגר נתונים זמני חדש שמתקבל באמצעות הקריאה tipc_chan_get_rxbuf())

Tipc_chan_connect()

התחלת חיבור לשירות Trusty IPC שצוין.

int tipc_chan_connect(struct tipc_chan *chan, const char *port);

[in] chan: מצביע לערוץ שהוחזר על ידי שיחה ב-tipc_create_chan()

[in] port: המצביע למחרוזת שמכילה את הפרמטר שם השירות שאליו רוצים לחבר

[retval]: 0 בשביל הצלחה, אחרת שגיאה שלילית

המתקשר יקבל הודעה כשנוצר חיבור באמצעות קבלת התקשרות חזרה handle_event.

Tipc_chan_shutdown()

ביטול החיבור לשירות Trusty IPC שהופעל בעבר באמצעות שיחת tipc_chan_connect().

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan: מצביע לערוץ שהוחזר על ידי שיחת tipc_create_chan()

tipc_chan_destroy()

הדבר חל על ערוץ Trusty IPC שצוין.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan: מצביע לערוץ שהוחזר על ידי שיחה ב-tipc_create_chan()

Tipc_chan_get_txbuf_timeout()

מקבלת מאגר הודעות זמני שיכול לשמש לשליחת נתונים . אם מאגר הנתונים הזמני לא זמין באופן מיידי, יכול להיות שהמתקשר/ת ייחסם למשך הזמן הקצוב לתפוגה שצוין (באלפיות השנייה).

struct tipc_msg_buf *
tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);

[in] chan: מצביע על הערוץ שאליו צריך להוסיף הודעה

[in] chan: זמן קצוב לתפוגה שצריך להמתין עד מאגר הנתונים הזמני של tx יהיה זמין

[retval]: מאגר הודעה תקף לגבי הצלחה, ERR_PTR(err) בשגיאה

Tipc_chan_queue_msg()

הוספת הודעה לתור שתישלח במקום שצוין ערוצים מהימנים של IPC.

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

[in] chan: מצביע על הערוץ שאליו צריך להוסיף את ההודעה

[in] mb: מצביע על ההודעה לתור (התקבל באמצעות שיחת tipc_chan_get_txbuf_timeout())

[retval]: 0 בשביל הצלחה, אחרת שגיאה שלילית

tipc_chan_put_txbuf()

שחרור מאגר ההודעות שצוין ב-Tx שהושג בעבר באמצעות קריאה ל-tipc_chan_get_txbuf_timeout().

void tipc_chan_put_txbuf(struct tipc_chan *chan,
                         struct tipc_msg_buf *mb);

[in] chan: מצביע על הערוץ שאליו מאגר ההודעות הזה שייך למאגר הנתונים הזמני

[in] mb: העברה של העכבר מעל מאגר ההודעות כדי לשחרר

[retval]: ללא

Tipc_chan_get_rxbuf()

מקבלת מאגר הודעות חדש שאפשר להשתמש בו כדי לקבל הודעות דרך כערוץ ספציפי.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan: מצביע לערוץ שאליו שייך מאגר ההודעות

[retval]: מאגר הודעות תקף במצב תקין, ERR_PTR(err) בשגיאה

Tipc_chan_put_rxbuf()

משחרר מאגר הודעות ספציפי שהתקבל בעבר על ידי שיחת tipc_chan_get_rxbuf().

void tipc_chan_put_rxbuf(struct tipc_chan *chan,
                         struct tipc_msg_buf *mb);

[in] chan: מצביע לערוץ שאליו שייך מאגר ההודעות

[in] mb: מצביע על מאגר נתונים זמני לשחרור

[retval]: ללא