مرجع API قابل اعتماد

Trusty API هایی را برای توسعه دو دسته از برنامه ها / خدمات ارائه می دهد:

  • برنامه ها یا سرویس های قابل اعتمادی که روی پردازنده TEE اجرا می شوند
  • برنامه های معمولی/غیرقابل اعتماد که روی پردازنده اصلی اجرا می شوند و از خدمات ارائه شده توسط برنامه های مورد اعتماد استفاده می کنند

Trusty API به طور کلی سیستم ارتباطات بین فرآیندی Trusty (IPC) را توصیف می کند، از جمله ارتباطات با دنیای غیر ایمن. نرم‌افزاری که روی پردازنده اصلی اجرا می‌شود می‌تواند از Trusty API برای اتصال به برنامه‌ها/سرویس‌های قابل اعتماد و تبادل پیام‌های دلخواه با آن‌ها درست مانند یک سرویس شبکه از طریق IP استفاده کند. این به برنامه بستگی دارد که قالب داده و معنایی این پیام ها را با استفاده از یک پروتکل در سطح برنامه تعیین کند. تحویل قابل اطمینان پیام ها توسط زیرساخت Trusty (به شکل درایورهایی که روی پردازنده اصلی اجرا می شوند) تضمین می شود و ارتباط کاملاً ناهمزمان است.

پورت ها و کانال ها

پورت ها توسط برنامه های Trusty استفاده می شوند تا نقاط پایانی سرویس را در قالب یک مسیر نامگذاری شده که مشتریان به آن متصل می شوند، نشان دهند. این یک شناسه سرویس ساده و مبتنی بر رشته را برای مشتریان ارائه می دهد. قرارداد نامگذاری، نامگذاری به سبک DNS معکوس است، به عنوان مثال com.google.servicename .

هنگامی که یک کلاینت به یک پورت متصل می شود، مشتری یک کانال برای تعامل با یک سرویس دریافت می کند. سرویس باید یک اتصال ورودی را بپذیرد، و زمانی که قبول کرد، یک کانال نیز دریافت می کند. در اصل، پورت ها برای جستجوی سرویس ها استفاده می شوند و سپس ارتباط از طریق یک جفت کانال متصل (یعنی نمونه های اتصال در یک پورت) رخ می دهد. هنگامی که یک کلاینت به یک پورت متصل می شود، یک اتصال متقارن و دو جهته برقرار می شود. با استفاده از این مسیر تمام دوبلکس، کلاینت ها و سرورها می توانند پیام های دلخواه مبادله کنند تا زمانی که هر یک از طرفین تصمیم به قطع اتصال بگیرند.

فقط برنامه های مورد اعتماد سمت امن یا ماژول های هسته Trusty می توانند پورت ایجاد کنند. برنامه های در حال اجرا در سمت غیر ایمن (در دنیای عادی) فقط می توانند به خدمات منتشر شده توسط سمت امن متصل شوند.

بسته به نیازها، یک برنامه قابل اعتماد می تواند همزمان هم کلاینت و هم سرور باشد. یک برنامه قابل اعتماد که یک سرویس را منتشر می کند (به عنوان سرور) ممکن است نیاز به اتصال به سرویس های دیگر (به عنوان مشتری) داشته باشد.

Handle API

دسته‌ها اعداد صحیح بدون علامت هستند که منابعی مانند پورت‌ها و کانال‌ها را نشان می‌دهند، مشابه توصیفگرهای فایل در یونیکس. پس از ایجاد دستگیره ها، آنها در یک جدول دسته مخصوص برنامه قرار می گیرند و می توانند بعداً به آنها ارجاع دهند.

تماس‌گیرنده می‌تواند داده‌های خصوصی را با استفاده از روش set_cookie() با یک دسته مرتبط کند.

روش‌ها در Handle API

دسته ها فقط در زمینه یک برنامه معتبر هستند. یک برنامه نباید مقدار یک handle را به برنامه های دیگر منتقل کند مگر اینکه به صراحت مشخص شده باشد. فقط یک مقدار handle باید با مقایسه آن با INVALID_IPC_HANDLE #define, که یک برنامه می‌تواند به عنوان نشانه‌ای از نامعتبر بودن یا تنظیم نشده بودن یک دسته استفاده کند.

داده های خصوصی ارائه شده توسط تماس گیرنده را با یک دسته مشخص مرتبط می کند.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : هر دسته ای که توسط یکی از فراخوانی های API برگردانده می شود

cookie [in]: اشاره‌گر به داده‌های فضای کاربر دلخواه در برنامه Trusty

[reval]: NO_ERROR در صورت موفقیت، < 0 کد خطا در غیر این صورت

این فراخوانی برای رسیدگی به رویدادها زمانی مفید است که در زمان دیگری پس از ایجاد دسته رخ دهند. مکانیزم مدیریت رویداد دسته و کوکی آن را به کنترل کننده رویداد برمی گرداند.

با استفاده از wait() water می‌توان برای رویدادها منتظر دستگیره‌ها بود.

صبر کن()

منتظر می ماند تا یک رویداد در یک دسته معین برای مدت زمان مشخصی رخ دهد.

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

[in] handle_id : هر دسته ای که توسط یکی از فراخوانی های API برگردانده می شود

event [out] : اشاره گر به ساختار که نشان دهنده رویدادی است که روی این دسته رخ داده است

[in] timeout_msecs : مقدار وقفه در میلی ثانیه. مقدار -1 یک مهلت نامحدود است

[reval]: 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 : دسته برای از بین بردن

[بازیابی]: 0 در صورت موفقیت؛ در غیر این صورت یک خطای منفی

API سرور

یک سرور با ایجاد یک یا چند پورت با نام که نشان دهنده نقاط پایانی سرویس آن است شروع می شود. هر پورت با یک دسته نمایش داده می شود.

روش‌ها در 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]: به ​​کانالی (اگر غیرمنفی) است که در آن سرور می‌تواند پیام‌ها را با مشتری مبادله کند (یا کد خطا در غیر این صورت)

Client API

این بخش شامل متدهای موجود در Client API است.

روش‌ها در Client API

اتصال ()

اتصال به پورت مشخص شده با نام را آغاز می کند.

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 پیام

تماس‌های API پیام‌رسانی، ارسال و خواندن پیام‌ها را از طریق یک اتصال (کانال) از قبل ایجاد شده امکان‌پذیر می‌سازد. تماس‌های API Messaging برای سرورها و کلاینت‌ها یکسان است.

یک کلاینت با صدور یک تماس connect() یک handle به یک کانال دریافت می کند و یک سرور از یک call accept() که در بالا توضیح داده شد، یک handle کانال دریافت می کند.

ساختار یک پیام مطمئن

همانطور که در زیر نشان داده شده است، پیام‌های رد و بدل شده توسط 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 خواندن و نوشتن scatter-gather را در این بلوک ها انجام می دهد. محتوای بافرهایی که می توان با آرایه iov توصیف کرد کاملا دلخواه است.

روش‌ها در API پیام‌رسانی

send_msg()

پیامی را از طریق یک کانال مشخص ارسال می کند.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : به کانالی که پیام را از طریق آن ارسال می‌کنید

[in] msg : اشاره گر به ipc_msg_t structure که پیام را توصیف می کند

[reval]: تعداد کل بایت های ارسال شده در زمان موفقیت. در غیر این صورت یک خطای منفی

اگر کلاینت (یا سرور) بخواهد پیامی را از طریق کانال ارسال کند و فضایی در صف پیام همتای مقصد وجود نداشته باشد، ممکن است کانال وارد حالت مسدود شده ارسال شود (این هرگز نباید برای یک پروتکل درخواست/پاسخ همزمان ساده اتفاق بیفتد. اما ممکن است در موارد پیچیده‌تر اتفاق بیفتد) که با بازگرداندن کد خطای 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 در موفقیت؛ در غیر این صورت یک خطای منفی

پس از بازنشستگی پیام و بافری که آن را اشغال کرده آزاد شده است، نمی توان به محتوای پیام دسترسی داشت.

File Descriptor API

File Descriptor API شامل فراخوانی های read() ، write() و ioctl() می باشد. همه این فراخوان ها می توانند بر روی یک مجموعه از پیش تعریف شده (ایستا) از توصیفگرهای فایل که به طور سنتی با اعداد کوچک نمایش داده می شوند، عمل کنند. در پیاده سازی فعلی، فضای توصیفگر فایل جدا از فضای دسته IPC است. File Descriptor API در Trusty شبیه یک API سنتی مبتنی بر توصیفگر فایل است.

به طور پیش فرض، 3 توصیف کننده فایل از پیش تعریف شده (استاندارد و شناخته شده) وجود دارد:

  • 0 - ورودی استاندارد. اجرای پیش‌فرض ورودی استاندارد fd یک No-op است (چون انتظار نمی‌رود برنامه‌های مورد اعتماد کنسول تعاملی داشته باشند) بنابراین خواندن، نوشتن یا فراخوانی ioctl() در fd 0 باید خطای ERR_NOT_SUPPORTED را برگرداند.
  • 1 - خروجی استاندارد. بسته به پلتفرم و پیکربندی، داده های نوشته شده در خروجی استاندارد را می توان (بسته به سطح اشکال زدایی LK) به UART و/یا گزارش حافظه موجود در سمت غیر ایمن هدایت کرد. گزارش‌ها و پیام‌های اشکال‌زدایی غیر مهم باید در خروجی استاندارد قرار بگیرند. متدهای read() و ioctl() no-ops هستند و باید یک خطای ERR_NOT_SUPPORTED را برگردانند.
  • 2- خطای استاندارد. بسته به پلت فرم و پیکربندی، داده های نوشته شده با خطای استاندارد باید به UART یا گزارش حافظه موجود در سمت غیر ایمن هدایت شوند. توصیه می‌شود فقط پیام‌های مهم را با خطای استاندارد بنویسید، زیرا این جریان به احتمال زیاد از بین می‌رود. متدهای read() و ioctl() no-ops هستند و باید یک خطای ERR_NOT_SUPPORTED را برگردانند.

حتی اگر این مجموعه از توصیف‌گرهای فایل را می‌توان برای پیاده‌سازی fds بیشتر (برای اجرای پسوندهای خاص پلتفرم) گسترش داد، گسترش توصیف‌گرهای فایل باید با احتیاط اعمال شود. گسترش توصیف کننده فایل مستعد ایجاد تضاد است و به طور کلی توصیه نمی شود.

روش‌ها در File Descriptor API

خواندن()

تلاش برای خواندن تا count بایت داده از یک توصیفگر فایل مشخص.

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

[in] fd : توصیف کننده فایل که از آن می توان خواند

[out] buf : اشاره گر به بافری است که داده ها را در آن ذخیره می کند

[in] count : حداکثر تعداد بایت برای خواندن

[retval]: تعداد بایت های برگردانده شده خوانده شده. در غیر این صورت یک خطای منفی

نوشتن()

می‌نویسد تا 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 متفرقه

روش‌ها در API متفرقه

gettime()

زمان فعلی سیستم (بر حسب نانوثانیه) را برمی‌گرداند.

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

[in] clock_id : وابسته به پلتفرم؛ برای پیش فرض صفر را پاس کنید

[in] flags : رزرو شده، باید صفر باشد

[out] time : اشاره‌گر به یک مقدار int64_t است که زمان فعلی را روی آن ذخیره می‌کند

[reval]: NO_ERROR در موفقیت؛ در غیر این صورت یک خطای منفی

nanosleep()

اجرای برنامه فراخوان را برای مدت معینی به حالت تعلیق در می آورد و پس از آن مدت آن را از سر می گیرد.

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

[in] clock_id : رزرو شده، باید صفر باشد

[in] 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() 10000 پیام را به صورت ناهمزمان به سرویس "echo" ارسال می کند و پاسخ ها را مدیریت می کند.

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 توسط درایور کرنل trusty-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() )
  • برای بازیابی پیام های دریافتی قابل خواندن است

تماس گیرنده با اجرای یک تماس نوشتن برای fd مشخص شده، پیامی به سرویس Trusty ارسال می کند. تمام داده های ارسال شده به write() بالا توسط درایور trusty-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() باز شده است

Kernel Trusty IPC Client API

هسته Trusty IPC Client API برای درایورهای هسته در دسترس است. فضای کاربری Trusty IPC API در بالای این API پیاده سازی شده است.

به طور کلی، استفاده معمولی از این API شامل ایجاد یک شی struct tipc_chan توسط تماس گیرنده با استفاده از تابع tipc_create_channel() و سپس استفاده از فراخوانی tipc_chan_connect() برای شروع اتصال به سرویس Trusty IPC است که در سمت امن اجرا می‌شود. اتصال به سمت راه دور را می توان با فراخوانی tipc_chan_shutdown() و سپس tipc_chan_destroy() برای پاکسازی منابع قطع کرد.

پس از دریافت یک اعلان (از طریق callback handle_event() ) مبنی بر اینکه یک اتصال با موفقیت برقرار شده است، تماس گیرنده کارهای زیر را انجام می دهد:

  • با استفاده از تماس tipc_chan_get_txbuf_timeout() یک بافر پیام به دست می آورد
  • یک پیام می نویسد، و
  • پیام را با استفاده از روش tipc_chan_queue_msg() برای تحویل به یک سرویس Trusty (در سمت امن)، که کانال به آن متصل است، در صف قرار می دهد.

پس از موفقیت آمیز بودن صف، تماس گیرنده باید بافر پیام را فراموش کند زیرا بافر پیام در نهایت پس از پردازش توسط سمت راه دور (برای استفاده مجدد در آینده، برای پیام های دیگر) به مخزن بافر آزاد باز می گردد. کاربر فقط باید tipc_chan_put_txbuf() را فراخوانی کند اگر نتواند چنین بافری را در صف قرار دهد یا دیگر مورد نیاز نباشد.

یک کاربر API پیام‌ها را از سمت راه دور دریافت می‌کند و با مدیریت یک تماس اعلان handle_msg() (که در چارچوب صف کاری trusty-ipc rx فراخوانی می‌شود) که یک اشاره‌گر به یک بافر rx حاوی یک پیام دریافتی برای رسیدگی ارائه می‌کند.

انتظار می‌رود پیاده‌سازی callback handle_msg() یک اشاره‌گر را به یک struct tipc_msg_buf برگرداند. اگر به صورت محلی مدیریت شود و دیگر مورد نیاز نباشد، می تواند مانند بافر پیام ورودی باشد. از طرف دیگر، اگر بافر ورودی برای پردازش بیشتر در صف قرار گیرد، می‌تواند یک بافر جدید باشد که توسط یک فراخوانی tipc_chan_get_rxbuf() بدست می‌آید. یک بافر rx جدا شده باید ردیابی شود و در نهایت با استفاده از یک فراخوانی tipc_chan_put_rxbuf() زمانی که دیگر مورد نیاز نیست آزاد شود.

روش‌ها در Kernel Trusty IPC Client API

tipc_create_channel()

نمونه ای از کانال 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 ، با تماس‌های تماس‌گیرنده خاص پر شده است

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

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

اتصال به سرویس Trusty IPC را که قبلاً با تماس tipc_chan_connect() آغاز شده بود، خاتمه می دهد.

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : اشاره گر به کانالی است که با تماس tipc_create_chan() نشانگر است

tipc_chan_destroy()

یک کانال IPC معین Trusty را از بین می برد.

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

پیامی را در صف می‌گذارد تا از طریق کانال‌های Trusty IPC مشخص شده ارسال شود.

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

[in] 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 : اشاره گر به بافر پیام برای انتشار

[reval]: هیچ

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 : اشاره گر به یک بافر پیام برای انتشار

[reval]: هیچ