محاكاة شبكات Thread باستخدام OTNS

1. مقدمة

صورة لنموذج شبكة متداخلة من Thread

ما هو بروتوكول Thread وOTNS؟

‫Thread هو بروتوكول شبكة لاسلكية منخفضة الطاقة مستند إلى بروتوكول الإنترنت (IP) ويتيح إجراء اتصالات آمنة بين الأجهزة وبين الأجهزة والسحابة الإلكترونية. يمكن لشبكات Thread التكيّف مع تغييرات طوبولوجية لتجنُّب نقطة تعطل واحدة.

OpenThread الذي أصدرته Google هو تطبيق مفتوح المصدر لبروتوكول Thread. على الرغم من صغر حجم الرمز البرمجي واستهلاك الذاكرة، يتيح OpenThread جميع الميزات المحدّدة في مواصفات Thread.

يمكن استخدام محاكي شبكة OpenThread (OTNS) لمحاكاة شبكات Thread من خلال تشغيل عُقد OpenThread المحاكية على منصات posix. توفّر أداة OTNS واجهة ويب سهلة الاستخدام (OTNS-Web) لعرض شبكات Thread المحاكاة وتشغيلها. يمكن أيضًا إجراء عمليات محاكاة باستخدام نصوص برمجية (باستخدام Python).

أهداف الدورة التعليمية

  • تثبيت OTNS والموارد التابعة
  • التعرّف على أساسيات OTNS-CLI
  • كيفية إضافة/نقل/حذف عُقد OpenThread في OTNS-Web
  • استخدام ميزات OTNS-Web المفيدة الأخرى للتحكّم في محاكاة الشبكة
  • التحقّق من عدم وجود نقطة تعطل واحدة في OpenThread
  • الاطّلاع على حركة البيانات بين عُقد OpenThread في Wireshark

يركّز هذا الدرس التطبيقي حول الترميز على OTNS-CLI وOTNS-Web للاستخدام التفاعلي. لا يشمل ذلك ميزات OTNS الأخرى، مثل برمجة نصوص Python.

المتطلبات

  • مقدمة حول سلاسل المحادثات يجب أن تكون على دراية بالمفاهيم الأساسية لـ Thread لفهم ما يتم تدريسه في هذا الدرس التطبيقي.
  • يُفضّل استخدام Linux x86_64 أو Mac OS مع Homebrew. من المفترض أن يعمل الإصدار 24 من Ubuntu أو الإصدارات الأحدث في WSL2 على Windows أيضًا، ولكن قد يتطلّب ذلك بعض التعديلات اليدوية في الإعدادات.
  • Git
  • متصفّح الويب يستخدم OTNS-Web متصفّح ويب لعرض عمليات المحاكاة.
  • أداة تحليل بروتوكول الشبكة Wireshark (اختيارية)
  • الإصدار 1.23 أو إصدار أحدث من Go
    • سيتحقّق النص البرمجي للتثبيت من إصدار Go المثبَّت.
    • إذا لم يكن Go مثبَّتًا، سيتم تثبيت إصدار >= 1.23، إذا كان متاحًا من مدير الحزم.
    • إذا لم يكن متاحًا من مدير الحزم، يجب تثبيته يدويًا.
    • يُرجى العِلم أنّ الإصدار Ubuntu 24.04 أو الإصدارات الأقدم لا تتوافق مع Go 1.23 تلقائيًا. للحصول على التفاصيل، يُرجى الاطّلاع على صفحة إصدارات Golang المتاحة في مستندات Ubuntu. يمكن إجراء عملية التثبيت يدويًا أو باستخدام snap أو إجراءات أخرى.
  • الإصدار 3.9 أو الإصدارات الأحدث من Python
    • سيتحقّق النص البرمجي للتثبيت من إصدار Python المثبَّت.
    • إذا لم يكن الإصدار 3 من Python مثبَّتًا بعد، سيتم تثبيت إصدار >= 3.9 إذا كان متاحًا من مدير الحزم.
    • إذا لم يكن متاحًا من مدير الحزم، يجب تثبيته يدويًا.

المصطلحات

يُستخدم المصطلح "جهاز توجيه" كاسم تقني لجهاز توسيع شبكة Thread، والذي كان يُطلق عليه في البداية اسم "جهاز توجيه Thread". يشير مصطلح "عقدة" إلى أي جهاز OpenThread محاكى في محاكاة OTNS.

2. تثبيت

الحصول على رمز OTNS

$ git clone https://github.com/openthread/ot-ns.git ./otns
$ cd otns

يتم تنفيذ جميع أوامر وحدة التحكّم اللاحقة في هذا الدرس العملي من دليل otns.

الإعداد والتثبيت

سيثبِّت النص البرمجي bootstrap التبعيات (بما في ذلك Python 3 وGo/Golang، إذا لزم الأمر) ويثبِّت OTNS. يُرجى العِلم أنّ البرنامج النصي قد يتوقف إذا لم يتمكّن من تثبيت بعض التبعيات تلقائيًا، مثل إصدار Python >= 3.9 أو إصدار Go >= 1.23. يتطلّب التثبيت التلقائي إمكانية العثور على الحِزم في مستودع الحِزم الذي تم ضبطه في نظام التشغيل.

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

$ ./script/bootstrap
....
....
OTNS installed - use 'otns' to start it.
$

قد يُطلب منك إدخال كلمة مرور sudo أثناء تنفيذ النص البرمجي.

إذا لم يتم تثبيت otns بشكل صحيح

قد يعرض النص البرمجي رسالة خطأ مثل:

....
OTNS installed - please add ~/go/bin to your PATH variable first, to use it.
$

في هذه الحالة، عليك إضافة $(go env GOPATH)/bin إلى المتغيّر $PATH.

في حال حدوث أخطاء أخرى، يمكن إنشاء مشكلة في GitHub.

3- تشغيل OTNS للمرة الأولى

تشغيل otns:

$ otns
>_ ← OTNS-CLI prompt

عند بدء تشغيل OTNS بنجاح، سيتم فتح وحدة تحكّم في واجهة سطر الأوامر (OTNS-CLI) وتشغيل متصفّح ويب لعرض الشبكة وإدارتها (OTNS-Web):

نافذة OTNS-Web عند البدء

إذا كنت ترى صفحة فارغة فقط في OTNS-Web، من المحتمل أنّ WebGL غير مفعَّلة في متصفّحك. يُرجى الرجوع إلى https://superuser.com/a/836833 لمعرفة كيفية تفعيل WebGL.

في الأقسام التالية، ستتعرّف على كيفية إدارة عمليات محاكاة OTNS من خلال OTNS-CLI وOTNS-Web.

4. التعرّف على OTNS-CLI وOTNS-Web

OTNS-CLI

OTNS-CLI هي واجهة سطر الأوامر (CLI) لإدارة عمليات محاكاة OTNS.

$ otns
>_ ← OTNS-CLI prompt

يمكنك كتابة الأوامر من خلال OTNS-CLI. راجِع مرجع واجهة سطر الأوامر لأداة OTNS للاطّلاع على قائمة كاملة بالأوامر. لا تقلق، لن تستخدم سوى عدد قليل من هذه الأوامر في هذا الدرس العملي.

اكتب الأمر help للحصول على نظرة عامة على أوامر واجهة سطر الأوامر. هذه القائمة مطابقة لمرجع واجهة سطر الأوامر.

> help
add             Add a node to the simulation and get the node ID.
....
....
Done
> 

للحصول على مزيد من المساعدة بشأن أمر معيّن، استخدِم اسم الأمر، مثلاً:

> help add
add
  Add a node to the simulation and get the node ID.
  
Definition:
....
....
> 

OTNS-Web

OTNS-Web هي أداة إدارة وتصوّر الشبكة في OTNS. تقدّم هذه الأداة تمثيلاً مرئيًا للعُقد والرسائل والروابط في شبكة Thread المحاكية. لاحظ العناصر المختلفة في OTNS-Web:

شرح عناصر OTNS-Web

5- إضافة عُقد

إضافة عُقد من خلال OTNS-CLI

أضِف جهاز توسيع شبكة Mesh (باستخدام نوع العُقدة router) إلى المحاكاة:

> add router
1
Done

من المفترض أن تظهر عقدة تم إنشاؤها في OTNS-Web. تبدأ العُقدة كجهاز توسيع لشبكة متداخلة وتصبح جهازًا رئيسيًا في غضون بضع ثوانٍ:

عُقدة واحدة في دور القائد

لتسهيل بدء عمليات المحاكاة بشكل تفاعلي، يتم إعداد كل عقدة OpenThread جديدة تلقائيًا باستخدام مجموعة عادية من مَعلمات الشبكة.

إضافة المزيد من العُقد من خلال OTNS-CLI

الآن سنضيف بعض العُقد من أنواع مختلفة.

> add fed
2
Done
> add med
3
Done
> add sed
4
Done

انتظِر بضع ثوانٍ حتى يتم دمج العُقد في قسم واحد. من المفترض أن تظهر لك العُقد في OTNS-Web:

شبكة Thread تضم 4 عُقد

في OTNS-Web أيضًا، يمكنك اختيار أي من العُقد للحصول على لوحة تتضمّن المزيد من المعلومات حول العُقدة. على سبيل المثال، في الشكل أدناه، تم اختيار العقدة 1. يؤكّد الإدخال "الدور" في اللوحة أنّ المستخدم هو "قائد".

شبكة Thread تضم 4 عُقد، وتم اختيار العقدة 1

إضافة عُقد حسب OTNS-Web

يمكنك أيضًا إضافة عُقد من خلال OTNS-Web. انقر على الزر New Router في Action Bar. من المفترض أن تظهر عقدة يتم إنشاؤها على يسار العقدة المحدّدة. يجب أن ينضم جهاز Mesh Extender الجديد إلى قسم Thread الحالي:

تمت إضافة موسِّع شبكة متداخلة، ليصبح إجمالي عدد العُقد 5

يمكنك أيضًا النقر على أزرار FED وMED وSSED وBR في "شريط الإجراءات" لإنشاء هذه الأنواع الأخرى من العُقد. يجب أن يكون هناك 9 عُقد إجمالاً. إذا أردت، يمكنك سحب بعض العُقد إلى مواضع أخرى لإنشاء مخطط طوبولوجي مختلف للشبكة الفعلية.

تمت إضافة عُقد جديدة متعددة، بإجمالي 9 عُقد

لقد أنشأت الآن شبكة Thread من قسم واحد يحتوي على العديد من العُقد. في القسم التالي، سنعدّل سرعة المحاكاة لتسريع عملية المحاكاة.

6. ضبط السرعة

في الوقت الحالي، يجب أن تعمل المحاكاة بسرعة 1X، ما يعني أنّ الوقت الذي انقضى حتى الآن في المحاكاة هو نفسه الوقت الفعلي منذ أن أنشأنا العقدة الأولى.

ضبط السرعة من خلال OTNS-CLI

يمكنك ضبط سرعة المحاكاة من خلال OTNS-CLI.

ضبط سرعة المحاكاة على 100X

> speed 100
Done

من المفترض أن ترى العُقد ترسل الرسائل بشكل متكرر أكثر من السابق.

ضبط سرعة المحاكاة على MAX

> speed max
Done

الآن، تحاول شبكة OTNS محاكاة أكبر عدد ممكن من الرسائل بأسرع ما يمكن، لذا من المفترض أن ترى العُقد ترسل عددًا كبيرًا من الرسائل.

إيقاف المحاكاة

> speed 0
Done

يؤدي ضبط سرعة المحاكاة على 0 إلى إيقاف المحاكاة مؤقتًا.

استعادة المحاكاة بالسرعة العادية

> speed 1
Done

يؤدي ضبط سرعة المحاكاة على قيمة أكبر من 0 إلى استئناف المحاكاة.

ضبط السرعة من خلال OTNS-Web

أزرار التحكّم في السرعة

ابحث عن أزرار التحكّم في السرعة أزرار التحكّم في السرعة على Action Bar. تعرض الأزرار سرعة المحاكاة الحالية ويمكن استخدامها لضبط سرعة المحاكاة وإيقاف المحاكاة مؤقتًا أو استئنافها.

تسريع المحاكاة

يمكنك تسريع المحاكاة من خلال النقر على الزر زر زيادة السرعة إلى أن تصل السرعة إلى MAX: مؤشر أقصى سرعة محاكاة.

إبطاء المحاكاة

يمكنك إبطاء المحاكاة من خلال النقر على الزر زر خفض السرعة.

إيقاف المحاكاة

انقر على الزر زر الإيقاف المؤقت لإيقاف المحاكاة مؤقتًا أثناء تشغيلها. سيتم تغيير الزر إلى الزرّ "تشغيل".

استئناف المحاكاة

انقر على الزر الزرّ "تشغيل" لاستئناف المحاكاة عندما تكون متوقفة مؤقتًا. سيتم تغيير الزرّ مرة أخرى إلى زر الإيقاف المؤقت.

ضبط سرعة المحاكاة على 10X

لتوفير الوقت، استخدِم

OTNS-CLI لضبط سرعة المحاكاة على

10X حتى نتمكّن من رصد التغييرات في بنية الشبكة بشكل أسرع بكثير.

> speed 10
Done

7. تفعيل/إيقاف الراديو

يجب أن يحتوي المحاكي الآن على جهازَي Mesh Extender على الأقل (شكل سداسي)، وربما جهاز Border Router (شكل مربّع) والعديد من الأجهزة الفرعية، وأن يعمل بسرعة 10 أضعاف.

ابحث عن الجهاز الرئيسي الحالي (حد أحمر) من بين جهازَي توسيع شبكة Wi-Fi، وانقر عليه نقرة واحدة لاختياره:

تم اختيار شبكة Thread مع العقدة الرئيسية 1

إيقاف اللاسلكي

انقر على الزر زر "إيقاف الاتصالات اللاسلكية" في شريط الإجراءات لإيقاف راديو عقدة Leader. لن يتمكّن "الجهاز الرئيسي" من إرسال الرسائل أو تلقّيها عندما يكون الراديو مطفأً.

انتظِر لمدة 12 ثانية تقريبًا (120 ثانية في وقت المحاكاة) ليصبح جهاز توجيه الحدود أو موسّع شبكة Mesh الآخر هو الجهاز الرئيسي الجديد:

يتم إنشاء قسم جديد باستخدام العقدة 9 كقائد جديد

تستعيد شبكة Thread عافيتها تلقائيًا من تعذُّر وصول القائد من خلال إنشاء قسم جديد مع قائد جديد. يحتوي القسم الجديد أيضًا على لون جديد.

تشغيل اللاسلكي

اختَر "القائد" الذي تم إيقاف الراديو الخاص به. انقر على الزر زر تشغيل الراديو في Action Bar لاستعادة اتصال الراديو:

تنضم العقدة 1 إلى القسم بعد إعادة تشغيل الراديو

يجب أن يعيد الجهاز الرئيسي الاتصال بالشبكة بعد استعادة الاتصال اللاسلكي.

8. نقل العُقد

تتيح أداة OTNS للمستخدمين نقل العُقد بسهولة من خلال OTNS-CLI أو OTNS-Web.

نقل العُقدة من خلال OTNS-CLI

انقل العقدة Border Router 9 إلى موقع جديد:

> move 9 50 50
Done

نقل العقدة من خلال OTNS-Web

انقل العقدة 5 إلى أسفل اليسار عن طريق السحب. بما أنّ العقدة 5 أصبحت خارج نطاق تغطية أجهزة Mesh Extender الأخرى، فإنّها تشكّل قسمًا خاصًا بها باستخدام رقم تعريف قسم جديد. يمكن التحقّق من أرقام تعريف الأقسام في لوحة معلومات العُقد من خلال النقر على العُقد.

يتم نقل العقدة 5 بعيدًا عن العُقد الأخرى وتشكيل قسم جديد.

يُرجى العِلم أنّه لا يزال يتم رسم خط أخضر واحد بين العقدة 5 والعقدة 9. يرجع ذلك غالبًا إلى معلومات قديمة عن الطفل، والتي لا تزال محفوظة في جدول الأطفال الخاص بأحد الوالدَين السابقَين. أو قد تكون معلومات قديمة حول رابط Mesh Extender السابق بين العقدة 9 والعقدة 5. (أو ربما، في هذه الحالة، حتى خطأ في العرض). في النهاية، يتم تنظيف المعلومات القديمة على العُقد بعد انتهاء المهلة المناسبة.

9. حذف العُقد

حذف العُقد من خلال OTNS-CLI

حذف العقدة 5:

> del 5
Done

يجب أن تختفي العُقدة 5 من المحاكاة:

تم حذف العقدة 5 من المحاكاة

حذف العُقد من خلال OTNS-Web

اختَر عقدة جهاز توجيه الحدود 9 وانقر على الزر زر الحذف في Action Bar لحذف العقدة 9:

تم حذف العقدة 9 لجهاز توجيه الحدود

يجب أن يصبح Node 1 قائدًا لقسم جديد، وسيتم ربط جميع العُقد المتبقية كعُقد فرعية بالعقدة 1.

10. OTNS-CLI Node Context

توفّر OTNS-CLI وضع سياق العُقد لتسهيل التفاعل مع العُقد ومساعدة المطوّرين في تشخيص حالة العُقدة. يمكن أيضًا بدء إجراءات العُقد من هذا الوضع.

تفعيل وضع سياق العقدة

أدخِل سياق العقدة 1:

> node 1
Done
node 1>

تغيّرت مطالبة واجهة سطر الأوامر إلى node 1>، ما يشير إلى سياق العقدة الحالي. يمكنك كتابة أوامر OpenThread CLI لتنفيذها على العقدة كما لو كنت تتفاعل مع العقدة مباشرةً.

تنفيذ الأوامر في سياق العقدة

node 1> state
leader
Done
node 1> channel
11
Done
node 1> panid
0xface
Done
node 1> networkname
otns
Done
node 1> ipaddr
fdde:ad00:beef:0:0:ff:fe00:fc00
fdde:ad00:beef:0:0:ff:fe00:b400
fd00:f00d:cafe:0:2505:8719:3685:ebfb
fdde:ad00:beef:0:4fd9:b9ba:44e0:96cb
fe80:0:0:0:e86a:e07:ec97:777
Done

التبديل إلى سياق عقدة آخر

node 1> node 2
Done
node 2> 

الخروج من سياق العقدة

node 1> exit
Done
>

هناك طريقة بديلة للخروج من سياق العُقدة وهي الأمر node 0.

11. عرض سجلّات العُقد وعمليات التقاط الحِزم

سجلات عقدة OpenThread

تنشئ أداة OTNS تلقائيًا ملفات سجلّ تفصيلية لجميع عُقد OpenThread المحاكاة. يمكن الاطّلاع على هذه الملفات في الدليل ./tmp. اسم الملف هو 0_.log. في ما يلي مثال على مقتطف من ملف سجلّ:

7616488 00:00:06.326 [I] MeshForwarder-: Received IPv6 UDP msg, len:90, chksum:5915, ecn:no, from:ca72650db7b856af, sec:no, prio:net, rss:-58.0
7616488 00:00:06.326 [I] MeshForwarder-:     src:[fe80:0:0:0:c872:650d:b7b8:56af]:19788
7616488 00:00:06.326 [I] MeshForwarder-:     dst:[ff02:0:0:0:0:0:0:1]:19788
7616488 00:00:06.326 [D] Mle-----------: Receive MLE message
7616488 00:00:06.326 [D] Mac-----------: Idle mode: Radio receiving on channel 11
7657544 00:00:06.367 [D] Mac-----------: ==============================[RX len=063]==============================
7657544 00:00:06.367 [D] Mac-----------: | 41 D8 7F CE FA FF FF 46 | 74 5A 33 9E 76 51 4E 7F | A......FtZ3.vQN. |
7657544 00:00:06.367 [D] Mac-----------: | 3B 02 F0 4D 4C 4D 4C 81 | E6 00 15 03 00 00 00 00 | ;..MLML......... |
7657544 00:00:06.367 [D] Mac-----------: | 00 00 00 01 46 86 7D FE | 06 CC DB 94 86 9C 88 0B | ....F.}......... |
7657544 00:00:06.367 [D] Mac-----------: | 1C 1E 26 9B 8D 21 2E 65 | 53 5A 43 4E A2 59 D6    | ..&..!.eSZCN.Y.  |
7657544 00:00:06.367 [D] Mac-----------: ------------------------------------------------------------------------
7657544 00:00:06.367 [I] MeshForwarder-: Received IPv6 UDP msg, len:84, chksum:81e6, ecn:no, from:4e51769e335a7446, sec:no, prio:net, rss:-48.0
7657544 00:00:06.367 [I] MeshForwarder-:     src:[fe80:0:0:0:4c51:769e:335a:7446]:19788
7657544 00:00:06.367 [I] MeshForwarder-:     dst:[ff02:0:0:0:0:0:0:2]:19788
7657544 00:00:06.367 [D] Mac-----------: Idle mode: Radio receiving on channel 11
7833912 00:00:06.543 [I] Mle-----------: AttachState ParentReq -> Idle
7833912 00:00:06.543 [N] RouterTable---: Allocate router id 12
7833912 00:00:06.543 [N] Mle-----------: RLOC16 fffe -> 3000
7833912 set node RLOC16: fffe -> 3000
7833912 00:00:06.543 [D] SubMac--------: RadioShortAddress: 0x3000
7833912 00:00:06.543 [N] Mle-----------: Role detached -> leader
7833912 00:00:06.543 [N] Mle-----------: Partition ID 0x24c35f10
7833912 00:00:06.543 [I] RouterTable---: Route table
7833912 00:00:06.543 [I] RouterTable---:     12 0x3000 - me - leader

يظهر وقت المحاكاة المطلق بالميكروثانية على اليمين. يعرض الطابع الزمني hh:mm:ss الطابع الزمني لسجلّ عقدة OpenThread، والذي قد يختلف عن وقت المحاكاة المطلق.

عمليات التقاط حِزم بيانات Wireshark

يتم تلقائيًا تسجيل جميع إطارات IEEE 802.15.4 المُرسَلة في ملف PCAP current.pcap. يمكن قراءة هذا الملف باستخدام Wireshark أثناء المحاكاة أو بعدها. بسبب تشفير طبقة الربط في Thread، يجب اتّخاذ إجراء إعداد لمرة واحدة في Wireshark لضبط مفتاح فك التشفير في OTNS بشكلٍ صحيح. يتم تلقائيًا استخدام مفتاح شبكة معروف لتسهيل فك تشفير الإطارات باستخدام Wireshark.

اطّلِع على لقطة الشاشة أدناه للحصول على مثال على فحص حزمة OpenThread في Wireshark.

لقطة شاشة لتحليل حِزم OpenThread في Wireshark

لضبط مفتاح فك التشفير، انقر على تعديل -> الإعدادات المفضّلة في القائمة. بعد ذلك، في نافذة الإعدادات المفضّلة، اختَر البروتوكولات -> IEEE 802.15.4. انقر على الزر تعديل... بجانب "مفاتيح فك التشفير". انقر على + لإنشاء إدخال جديد وأدخِل المفتاح 00112233445566778899aabbccddeeff (32 حرفًا) واختَر "تجزئة سلسلة المحادثات" في الحقل "تجزئة المفتاح". يمكن ترك "فهرس مفتاح فك التشفير" على القيمة 0. بعد ذلك، انقر على حسنًا، ثم على حسنًا مرة أخرى. من المفترض الآن أن يتم فك تشفير ملف OTNS PCAP بشكل صحيح عند تحميله.

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

12. تهانينا

تهانينا، لقد نفّذت بنجاح أول محاكاة لشبكة OTNS.

لقد تعلّمت كيفية تثبيت OTNS والبرامج التابعة لها. بدأت محاكاة OTNS باستخدام عُقد OpenThread المحاكية. لقد تعلّمت كيفية معالجة المحاكاة بطرق مختلفة من خلال كل من OTNS-CLI وOTNS-Web.

أنت تعرف الآن ما هو OTNS وكيف يمكنك استخدامه لمحاكاة شبكات OpenThread.

ما هي الخطوات التالية؟

اطّلِع على بعض دروس الترميز التطبيقية هذه...

المستندات المرجعية