Reference API מהימן

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

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

ה-API של Trusty מתאר בדרך כלל את מערכת התקשורת בין-תהליכים 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

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

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

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

ניתן להמתין לידיות לאירועים באמצעות הקריאה wait() .

לַחֲכוֹת()

ממתין לאירוע שיתרחש בנקודת אחיזה נתונה למשך פרק זמן מוגדר.

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

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

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

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

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

[reval]: 0 אם הצלחה; שגיאה שלילית אחרת

API של שרת

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

שיטות ב- Server API

port_create()

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

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

[in] path : שם המחרוזת של היציאה (כמתואר לעיל). שם זה צריך להיות ייחודי בכל המערכת; ניסיונות ליצור כפיל ייכשלו.

[in] num_recv_bufs : המספר המרבי של מאגרים שערוץ ביציאה זו יכול להקצות מראש כדי להקל על חילופי הנתונים עם הלקוח. מאגרים נספרים בנפרד עבור נתונים המגיעים לשני הכיוונים, כך שציון 1 כאן פירושו שהקצאה מראש של חיץ 1 ושליחה וקבלה 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);

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

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

[reval]: טיפול לערוץ (אם לא שלילי) שבו השרת יכול להחליף הודעות עם הלקוח (או קוד שגיאה אחרת)

לקוח API

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

שיטות ב-Client API

לְחַבֵּר()

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

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

path [in] : שם של יציאה שפורסם על ידי יישום Trusty

[ב] 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 - אם מוגדר, יוזם חיבור אסינכרוני. יישום צריך לבצע סקר עבור ה-handle המוחזר (על ידי קריאת wait() לאירוע השלמת חיבור המצוין על ידי הסיבית IPC_HANDLE_POLL_READY שהוגדרה בשדה האירוע של מבנה uevent_t לפני תחילת הפעולה הרגילה.

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

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

לקוח מקבל טיפול לערוץ על ידי הוצאת קריאה ל- 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 הוא שרירותי לחלוטין.

שיטות ב-API של Messaging

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;

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

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

read_msg()

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

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

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

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

[in] offset : קיזוז לתוך ההודעה שממנה מתחילים לקרוא

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

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

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

put_msg()

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

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : ידית של הערוץ שבו ההודעה הגיעה

[in] msg_id : מזהה ההודעה שהופסקה

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

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

API של מתאר קבצים

ה-API של File Descriptor כולל קריאות read() , write() ו- ioctl() . כל השיחות הללו יכולות לפעול על קבוצה מוגדרת מראש (סטטית) של מתארי קבצים המיוצגים באופן מסורתי על ידי מספרים קטנים. ביישום הנוכחי, מרחב מתאר הקובץ נפרד ממרחב האחיזה של ה-IPC. ה-API של תיאור הקבצים ב-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);

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

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

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

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

לִכתוֹב()

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

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

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

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

[ב] 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 שונות

שיטות ב-API שונות

gettime()

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

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

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

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

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

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

ננו-שינה()

משעה את ביצוע הבקשה המתקשרת לפרק זמן מוגדר ומחדש אותה לאחר תקופה זו.

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

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

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

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

[reval]: 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. בקרנל, ה-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 : נתיב לצומת מכשיר ה-IPC Trusty כדי לפתוח

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

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

tipc_close()

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

int tipc_close(int fd);

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

Kernel Trusty IPC Client API

ממשק ה-API של Trusty IPC Client זמין עבור מנהלי התקנים של ליבה. מרחב המשתמש Trusty IPC API מיושם על גבי API זה.

באופן כללי, שימוש טיפוסי ב-API זה מורכב מקורא יוצר אובייקט struct tipc_chan על ידי שימוש בפונקציה tipc_create_channel() ולאחר מכן שימוש בקריאה tipc_chan_connect() כדי ליזום חיבור לשירות Trusty IPC הפועל בצד המאובטח. ניתן לסיים את החיבור לצד המרוחק על ידי קריאה tipc_chan_shutdown() ואחריו tipc_chan_destroy() כדי לנקות משאבים.

לאחר קבלת הודעה (דרך ה- handle_event() callback) על כך שחיבור נוצר בהצלחה, מתקשר עושה את הפעולות הבאות:

  • משיג מאגר הודעות באמצעות הקריאה tipc_chan_get_txbuf_timeout()
  • מחבר הודעה, ו
  • מעמיד את ההודעה בתור באמצעות שיטת tipc_chan_queue_msg() למסירה לשירות Trusty (בצד המאובטח), שאליו הערוץ מחובר

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

משתמש API מקבל הודעות מהצד המרוחק על ידי טיפול בהתקשרות חוזרת של הודעה handle_msg() (שנקראת בהקשר של תור העבודה של trusty-ipc rx ) המספקת מצביע למאגר rx המכיל הודעה נכנסת שיש לטפל בה.

צפוי שהיישום של handle_msg() callback יחזיר מצביע 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 : מצביע ל-trusty-ipc שעבורו נוצר ערוץ המכשיר

[in] ops : מצביע struct tipc_chan_ops , עם התקשרויות חוזרות ספציפיות למילוי

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

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

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

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

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

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

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

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

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

tipc_chan_connect()

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

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

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

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

[reval]: 0 על הצלחה, שגיאה שלילית אחרת

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

tipc_chan_shutdown()

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

int tipc_chan_shutdown(struct tipc_chan *chan);

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

tipc_chan_destroy()

הורס ערוץ אמין 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 : מצביע לערוץ שאליו ניתן לתור את ההודעה

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

[reval]: 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]: אין