گیبلڈورسے میں ایک گہرا غوطہ: ایک دوبارہ تیار کردہ بلوٹوتھ اسٹیک اینڈرائیڈ

اینڈرائیڈ بلوٹوتھ اسٹیک نے تقریباً ایک دہائی اس وجہ سے بننے کی کوشش میں گزاری ہے کہ گانے کے اچھے حصے کے دوران آپ کے ہیڈ فون کا کنکشن ختم ہو جاتا ہے۔

Gabeldorsche اس مسئلے کو آرکیٹیکچرل سطح پر حل کرنے کی گوگل کی کوشش ہے، پرانے کوڈ کے اوپر ایک اور پیچ لگا کر نہیں، بلکہ اسٹیک کے اندرونی حصوں کو بنیادی طور پر دوبارہ لکھ کر۔

یہ مضمون بتاتا ہے کہ Gabeldorsche اصل میں کیا ہے، اس کا فن تعمیر کیسے بنایا گیا ہے، اور ہر حصہ عمل درآمد کی سطح میں کیسے فٹ بیٹھتا ہے۔

ہم OS خلاصہ، ماڈیول سسٹم، تھریڈنگ اور قطار ماڈل، HCI پرت، پیکٹ پارسنگ جنریٹر، ACL ڈیٹا پاتھ، L2CAP، سیکورٹی، پڑوسی اور اسٹوریج ماڈیولز، شیم اور اگواڑے کی تہہ، بلڈ سسٹم، اور ٹیسٹ انفراسٹرکچر کو دیکھیں گے، ہر ایک کے لیے اصل کوڈ پیٹرن کے ساتھ۔

یہاں کا دائرہ اسٹیک کا اندرونی فن تعمیر ہے، نہ کہ عوامی Android بلوٹوتھ APIs جنہیں آپ کی ایپ کال کرتی ہے۔ اگر آپ نے کبھی سوچا ہے کہ درمیان میں کیا ہوتا ہے۔ BluetoothDevice.createBond() اصل ریڈیو، یہ وہ پرت ہے۔ یہ مضمون اس کی تفصیل سے وضاحت کرتا ہے۔

انڈیکس

شرطیں

جدید C++ (C++ 17 محاورے جیسے لیمبڈا، std::unique_ptrآپ کے پاس نیویگیشن سیمنٹکس اور ٹیمپلیٹ کی بنیادی باتیں) اور بلوٹوتھ پروٹوکول اسٹیک (HCI، L2CAP، ATT، GATT، کلاسک اور کم توانائی کے درمیان فرق) کا ایک کھردرا ذہنی ماڈل ہونا چاہیے۔

ایونٹ سے چلنے والی ہم آہنگی اور میسج پاس کرنے والی ہم آہنگی سے واقف ہونا بہت مددگار ہے، کیونکہ گیبلڈورس ان کو فعال طور پر استعمال کرتا ہے۔ ایک بار جب آپ ایپول یا ری ایکٹر اسٹائل ایونٹ لوپس کے سامنے آجائیں گے تو OS کی پرت مانوس محسوس ہوگی۔

آپ کو پہلے AOSP کو ہیک کرنے کی ضرورت نہیں ہے، لیکن یہ جان کر کہ AOSP بہت بڑا اور تعمیر کرنے میں سست ہے آپ کو جذباتی طور پر تیار کر سکتا ہے۔

Gabeldorsche کیا ہے اور یہ کیوں موجود ہے؟

Gabeldorsche، جسے عام طور پر GD کہا جاتا ہے، اینڈرائیڈ بلوٹوتھ اسٹیک کا دوبارہ منظم کردہ کور ہے۔ یہ نام کوڈ ناموں کے طور پر باویرین الپس میں جگہوں کے ناموں کو منتخب کرنے کی Google کی روایت کا حصہ ہے۔ اور ہاں، یہ ہے. تقریباً ہر کوئی اس کا تلفظ غلط کہتا ہے، جو شاید دلکشی کا حصہ ہے۔

یہ سورس ٹری میں بلوٹوتھ ماڈیول کے نیچے ہے۔ packages/modules/Bluetooth/system/gd/اپنے اصل گھر سے فارغ التحصیل ہونے کے بعد system/bt/gd/ جب بلوٹوتھ ایک اپڈیٹ ایبل مین لائن ماڈیول بن گیا۔

یہ جس اسٹیک کو تبدیل کرتا ہے اسے عام طور پر فلورائیڈ کہا جاتا ہے، جو پہلے بلیوڈروڈ تھا۔ اس پرانے ضابطے نے کام کیا، اس لحاظ سے کہ امید کے سہارے پل بھی کام کرتے ہیں۔

یہ عالمی ریاست کے بڑے جالوں کے ارد گرد بنایا گیا ہے، کال بیک چینز جن کے بارے میں سوچنا مشکل ہے، اور تھریڈنگ جو سب سسٹم کے لحاظ سے مختلف ہوتی ہے۔ اگر کوئی مسئلہ پیدا ہوتا ہے، تو اسے دوبارہ بنانا ایک سکے کا پلٹنا تھا، اور تنہائی میں انفرادی پرتوں کی جانچ کرنا تکلیف دہ یا ناممکن تھا۔ بلوٹوتھ بگز نان ڈیٹرمنسٹک بگز ہونے کی وجہ سے مشہور ہو گئے ہیں، اور نان ڈیٹرمنسٹک بگز ایسے کیڑے ہوتے ہیں جو پیداوار میں زندہ رہتے ہیں اور پھر غصے سے جائزے وصول کرتے ہیں۔

گیبلڈورشے کو چند مضبوط آراء کے گرد ڈیزائن کیا گیا ہے۔ ہر پرت کو واضح انحصار کے ساتھ ایک خود ساختہ ماڈیول ہونا چاہیے۔ ہم آہنگی پورے کوڈ میں بکھرے ہوئے مشترکہ تالے کے بجائے اچھی طرح سے طے شدہ دھاگوں کے ذریعے پیغامات کو منتقل کرنے کے بارے میں ہونی چاہئے۔ پیکٹ پارس کو رسمی وضاحتوں سے تیار کیا جانا چاہئے، ہاتھ سے لکھے ہوئے بائٹ ریاضی سے نہیں۔ اس کی وجہ یہ ہے کہ ہاتھ سے لکھا ہوا بائٹ ریاضی سیکیورٹی کیڑے متعارف کراتا ہے۔ ہر پرت کو ورچوئل کنٹرولر سے علیحدہ طور پر جانچنے کے قابل ہونا چاہیے، جس سے ایئربڈز سے پہلے مسلسل انضمام کو ریگریشن پکڑنے کی اجازت دی جائے۔ اور ہر چیز کو اتنا پورٹیبل ہونا چاہیے کہ وہ اینڈرائیڈ کے علاوہ کسی اور چیز پر چل سکے، جو کہ توقع سے زیادہ اہم ثابت ہوتی ہے۔

ایک مختصر تاریخ اور ہجرت کی حکمت عملی

Gabeldorsche کو 2019 اور 2020 کے ارد گرد ایک کثیر سالہ کوشش کے طور پر عوامی طور پر جاری کیا گیا تھا، لیکن کبھی بھی ایک عہد کے طور پر نہیں چلایا گیا۔ چونکہ بلوٹوتھ اسٹیک کو اربوں آلات میں جوہری طور پر تبدیل نہیں کیا جا سکتا، اس لیے رول آؤٹ کو بتدریج، الٹنے والا، اور تکلیف دہ بنانے کے لیے ڈیزائن کیا گیا ہے – بنیادی ڈھانچے کے کام کے لیے تین عظیم صفتیں۔

ہجرت ایک وقت میں ایک تہہ، نیچے سے اوپر تک ہوئی۔ HCI پرت پہلے GD پر گئی کیونکہ یہ کنٹرولر کے سب سے قریب ہے اور اس کی صاف ترین حد ہے۔ یہ پھر ACL اور کنکشن مینجمنٹ لیئرز، L2CAP، سیکورٹی، اور اسی طرح ترقی کرتا ہے۔

ہر پرت کو ایک جھنڈے کے بعد گیٹ کیا جاتا ہے، جس سے ڈیوائس کو ایک پرت کے نئے GD نفاذ کو چلانے کی اجازت ملتی ہے جبکہ اس کے اوپر اور نیچے کی پرتوں کے وراثت کے نفاذ کو جاری رکھا جاتا ہے۔ یہی وجہ ہے کہ سم پرتیں موجود ہیں، بعد میں بحث کی جائے گی۔

جھنڈا بذات خود ایک سسٹم پراپرٹی کے طور پر اور بعد میں ایک کنفگ فلیگ کے طور پر استعمال کیا گیا، اور ٹیم کو ایک چھوٹی آبادی کو GD پرت فراہم کرنے، کریش اور کنیکٹیویٹی میٹرکس کا مشاہدہ کرنے، اور اگر کچھ غلط ہو گیا تو فوراً پیچھے ہٹنے کی اجازت دی۔

چونکہ ہر پرت آزادانہ طور پر تبدیل کی جا سکتی تھی، اس لیے ہم اسٹیک ٹریس کو دیکھنے کے بجائے جھنڈے کو پلٹ کر ایک ہی پرت میں رجعت کو دو طرفہ کر سکتے ہیں۔ یہ غیر مسحور کن پرچم نظم و ضبط اس بات کا ایک بڑا حصہ ہے کہ دوبارہ لکھنا تباہی کے بغیر پیداوار تک کیوں پہنچ گیا، اور یہ مطالعہ کرنے کے قابل ہے یہاں تک کہ اگر یہ بلوٹوتھ کو چھوتا بھی نہیں ہے۔

پرتوں والا فن تعمیر

Gabeldorsche تہوں کے ڈھیر پر مشتمل ہے، ہر پرت ایک ماڈیول ہے جو اس کے نیچے کی پرت پر منحصر ہے۔ مندرجہ ذیل خاکہ مین پرتوں کو دکھاتا ہے، نیچے کے وائرلیس ہارڈ ویئر سے لے کر اوپر والے اینڈرائیڈ فریم ورک تک۔

        +------------------------------------------+
        |     Android Framework (Java / AIDL)      |
        +------------------------------------------+
        |     BTIF / BTA (legacy profile logic)    |
        +------------------------------------------+
        |     Shim layer (GD <~> legacy bridge)    |
        +------------------------------------------+
        |  GATT | Security | L2CAP | Neighbor      | 
        +------------------------------------------+
        |  AclManager | Controller | HciLayer      |   
        +------------------------------------------+
        |     hci_hal (HAL: AIDL / HIDL interface) |
        +------------------------------------------+
        |     Bluetooth Controller (radio chip)    |
        +------------------------------------------+

ڈایاگرام کو انحصار کی سیڑھی کے طور پر نیچے سے اوپر تک پڑھا جاتا ہے۔ کنٹرولر چپ جسمانی ٹرانسمیشن کے ذریعے HCI سے مراد ہے۔ کہ hci_hal پرت اینڈرائیڈ ہارڈویئر تجریدی انٹرفیس کو لپیٹ دیتی ہے، لہذا باقی اسٹیک کو اس بات کی پرواہ نہیں ہے کہ آیا ٹرانسپورٹ UART، USB، یا ورچوئل ساکٹ ہے۔

اس کے اوپر، HCI پرت کمانڈ فلو کنٹرول اور ڈیملٹی پلیکس ایونٹس کا انتظام کرتی ہے۔ Controller ماڈیول چپ کی فعالیت کو کیش کرتا ہے اور AclManager آپ کنکشن کے مالک ہیں۔

اوپری پرت L2CAP چینلز، سیکورٹی، ملحقہ آپریشنز جیسے کہ تلاش اور صفحہ اسکین، اور GATT ڈیٹا بیس کو نافذ کرتی ہے۔ شیم لیئر ایک عارضی پل ہے جو نئے GD ماڈیولز کو منتقلی کے دوران پرانے BTIF اور BTA پروفائل کوڈ کے ساتھ ایک ساتھ رہنے کی اجازت دیتا ہے۔ سب سے اوپر Android فریم ورک ہے جس کے ساتھ ایپلی کیشن بات چیت کرتی ہے۔

ایک اہم ساختی نقطہ یہ ہے کہ ہر پرت صرف واضح انٹرفیس کے ذریعے نیچے کی طرف پہنچتی ہے اور کبھی بھی دوسری تہوں کے اندرونی حصے میں نہیں پہنچتی۔

OS تجریدی پرت

بلوٹوتھ منطق کے وجود میں آنے سے پہلے، گیبلڈورشے نے اپنے چھوٹے آپریٹنگ سسٹم کے تجریدات کی وضاحت کی۔ os/ ڈائریکٹری یہ وہ بنیاد ہے جس پر باقی سب کچھ قائم ہے اور موجود ہے تاکہ اسٹیک اینڈرائیڈ، لینکس اور اندرونی ٹیسٹنگ پر بغیر کسی تبدیلی کے چل سکے۔

مندرجہ ذیل جدول میں کلیدی پرائمیٹوز کی فہرست دی گئی ہے اور ہر عنصر کیا کرتا ہے۔

اصل زبان ذمہ داری
Thread ایک ری ایکٹر کا مالک ہے اور قابل انتخاب شیڈولنگ ترجیح کے ساتھ ایونٹ لوپ چلاتا ہے۔
Reactor ایپل پر مبنی ایونٹ لوپ پاسنگ فائل ڈسکرپٹر کی تیاری کی حیثیت
Handler لاک فری سیریل ایگزیکیوشن کے لیے مخصوص تھریڈ پر بندش پوسٹ کریں۔
Alarm / RepeatingAlarm تاخیر کے بعد یا باقاعدہ وقفوں سے بند ہونے کا شیڈول بنائیں۔
Queue ذمہ دار اور محدود واحد پروڈیوسر واحد صارف چینل
EnqueueBuffer ایک مددگار جو اشیاء کو بفر کرتا ہے اور انہیں کھلاتا ہے۔ Queue مطالبہ پر

یہ جدول ہر OS کو ایک آپریشن کے لیے قدیم نقشہ بناتا ہے۔ Thread اور Reactor یہ پھانسی پر مبنی ہے۔ ایک دھاگہ ایک ری ایکٹر کے علاوہ اصل کرنل تھریڈ ہے جو اسے لپیٹتا ہے۔ Handler اس طرح دوسرے کوڈ کے نظام الاوقات تھریڈ پر اس کے اندرونی حصوں کو چھوئے بغیر کام کرتے ہیں۔ Alarm اور RepeatingAlarm ماڈل میں وقت شامل کریں۔ Queue اور EnqueueBuffer یہ ایک ڈیٹا موومنٹ ہے جو پروڈیوسر اور صارفین کو جوڑتی ہے۔

اس تہہ کے اوپر کی ہر چیز بالکل ان ٹکڑوں سے بنی ہے، لہذا ان کو سمجھنے سے آپ کے پورے کوڈ بیس کو فائدہ ہوگا۔

کوئی راستہ نہیں Thread جی ڈی میں، یہ صرف ایک خام دانا دھاگہ نہیں ہے۔ کہ Reactorیہ ایپل پر مبنی ایونٹ لوپ ہے۔ ساکٹ ریڈز کو مسدود کرنے کے بجائے، ہم ایک فائل ڈسکرپٹر کو ری ایکٹر کے ساتھ رجسٹر کرتے ہیں اور جب ڈسکرپٹر پڑھنے کے لیے دستیاب ہو جاتا ہے تو اس پر عمل کرنے کے لیے کال بیک پاس کرتے ہیں۔

ایک دھاگہ ایک ری ایکٹر لوپ کو گھماتا ہے اور اس تھریڈ کے تمام اعمال واقعات کے جواب میں ہوتے ہیں۔ تھریڈز کو نارمل یا ریئل ٹائم ترجیحات کے ساتھ بنایا جا سکتا ہے، اور اسٹیک ڈیٹا پاتھ میں تھریڈز کے لیے اعلیٰ ترجیحات کا استعمال کرتا ہے، اس لیے جب سسٹم مصروف ہو تو آڈیو نہیں نکلتا۔ یہ libevent یا Node.js ایونٹ لوپ جیسا ہی خیال ہے، آپ کو صرف بلوٹوتھ بیج پہننے کی ضرورت ہے۔

کہ Reactor یہ سمجھنے کے قابل ہے کیونکہ یہ ہر تھریڈ کا دھڑکتا دل ہے۔ اس میں ایک ایپل فائل ڈسکرپٹر اور رجسٹرڈ ری ایکٹو امکانات کا ایک سیٹ ہے، ہر ایک فائل ڈسکرپٹر کو پڑھنے پر کال بیک اور لکھنے پر ایک اختیاری کال بیک کے ساتھ جوڑتا ہے۔

لوپ کال epoll_waitہر تیار شدہ ڈسکرپٹر کے لیے رجسٹرڈ کال بیک کال کرتا ہے۔ ایک باریک تفصیل ڈی رجسٹریشن سیفٹی ہے۔ ایک کال بیک ری ایکٹر سے اپنے ری ایکٹو کو غیر رجسٹر کرنے کے لیے کہہ سکتا ہے جب کہ اسے بیچ میں بھیجا جا رہا ہو، اس لیے ری ایکٹر اس وقت چلنے والے ری ایکٹیو کا ٹریک رکھ سکتا ہے اور تباہی کو موخر کر سکتا ہے، استعمال کے بعد مفت کو روک سکتا ہے، جسے متحرک کرنا آسان نہیں ہے۔ ری ایکٹر انٹرنل کنٹرول فائل ڈسکرپٹر بھی استعمال کرتا ہے، تاکہ دوسرے تھریڈز اسے بیدار کر سکیں اور اسے مکمل طور پر روک سکیں۔

کوئی راستہ نہیں Handler کام کو کسی مخصوص دھاگے میں لانے کا طریقہ کار۔ جب آپ کسی ہینڈلر کو بندش پوسٹ کرتے ہیں تو بندش اس ہینڈلر کے تھریڈ پر چلتی ہے۔ اندرونی طور پر، ہینڈلر کے پاس ری ایکٹر میں ایک بند ہونے والی قطار اور ایک ایونٹ ایف ڈی رجسٹرڈ ہے، لہذا ری ایکٹر کو جگانے اور بند ہونے والی قطار کو خالی کرتے ہوئے، ایونٹ ایف ڈی کو پوسٹنگ ٹاسک لکھے جاتے ہیں۔

اس طرح پورا اسٹیک تالا لگانے سے گریز کرتا ہے۔ مشترکہ حالت کو چھونے کے لیے دو تھریڈز کے بجائے ایک میٹیکس کو پکڑنے کے لیے، ایک تھریڈ دوسرے تھریڈ کے ہینڈلر کو پیغام بھیجتا ہے، اور ریاست کو صرف اس دھاگے سے ٹچ کیا جاتا ہے جو اس کا مالک ہے۔

// Get the module's handler and post work onto its thread.
os::Handler* handler = GetHandler();

handler->Post(common::BindOnce(
    [](int connection_handle) {
      // This lambda runs on the handler's thread, not the caller's.
      LOG_INFO("Tearing down connection %d", connection_handle);
    },
    connection_handle));

یہ ٹکڑا اسٹیک کے مرکزی کنکرنسی پیٹرن کو ظاہر کرتا ہے۔ GetHandler() موجودہ ماڈیول سے وابستہ ہینڈلر کو ایک مخصوص دھاگے سے منسلک کرتا ہے۔ common::BindOnce کال ایبل کو اس کے دلائل کے ساتھ ون شاٹ کلوزر میں پیک کریں، جو کہ درج ذیل کی طرح ہے: std::bind تاہم، نقل و حرکت کی شناخت اور واحد استعمال کی صلاحیتیں اہم ہیں کیونکہ بہت سے بلوٹوتھ پے لوڈز صرف نقل و حرکت کے بفر ہیں۔ handler->Post ہینڈلر کے ایونٹ ایف ڈی کو لکھتا ہے اور قطار بناتا ہے جو ہینڈلر تھریڈ پر بند ہونا ہے۔ نتیجے کے طور پر، لیمبڈا باڈی ایک ہی دھاگے پر سیریلی طور پر چلتی ہے، جس سے اس ماڈیول کی حالت کو بغیر کسی mutex کے پڑھا اور لکھا جا سکتا ہے۔

اگر آپ Gabeldorsche کے بارے میں ایک چیز کو اندرونی بناتے ہیں، تو اسے رہنے دیں: ٹاسک ڈیٹا تھریڈ پر جاتے ہیں، لیکن ڈیٹا کاموں میں نہیں جاتا۔

وقت پر مبنی کاموں کے لیے Alarm اور RepeatingAlarm. الارم ایک ہی ری ایکٹر کے اوپر ایک ہینڈلر میں تاخیر کے بعد چلانے کے لیے بندش کو شیڈول کرکے اور ٹائمر ایف ڈی کا استعمال کرتے ہوئے بنائے جاتے ہیں، اس لیے ٹائمر صرف ایک اور فائل ڈسکرپٹر ہے جسے الگ ٹائمر تھریڈ کے بجائے لوپ میں پڑھا جا سکتا ہے۔

// Fire a one-shot timeout on this module's handler thread.
alarm_ = std::make_unique<:alarm>(GetHandler());

alarm_->Schedule(
    common::BindOnce(&MyModule::OnConnectionTimeout, common::Unretained(this)),
    std::chrono::milliseconds(5000));

یہاں ماڈیول ہینڈلر کے لیے الارم تیار ہوتا ہے جو کال بیک کو صحیح تھریڈ سے جوڑتا ہے۔ Schedule یہ ایک بندش اور مدت لیتا ہے، اس مدت کے لیے ٹائمر فائر کرتا ہے، اور جب یہ چلتا ہے، ری ایکٹر چلتا ہے۔ OnConnectionTimeout ہینڈلر تھریڈ میں۔

common::Unretained(this) بائنڈر کو ہدایت کرتا ہے کہ وہ اپنی زندگی میں توسیع کیے بغیر کسی شے کی طرف خام پوائنٹر رکھے۔ یہ یہاں بالکل محفوظ ہے کیونکہ الارم اور آبجیکٹ ایک ہی دھاگے پر ہیں اور معلوم ترتیب میں جاری کیے گئے ہیں۔

وہ آخری نکتہ اہم ہے۔ Unretained یہ ایک وعدہ ہے جو آپ مرتب کرنے والے سے کرتے ہیں، اور اگر آپ اسے توڑ دیتے ہیں، تو یادگار اور دور دراز دونوں طرح کا حادثہ ہو سکتا ہے۔

ماڈیول نظام

Gabeldorsche میں تمام فنکشنل پرتیں ہیں۔ Module. ماڈیولز کا لائف سائیکل، انحصار کا سیٹ، اور ان کا اپنا ہینڈلر تھریڈ وابستگی ہے۔ کہ ModuleRegistry یہ ماڈیولز کو ان کے انحصار کی ترتیب میں شروع کرنے اور الٹ ترتیب میں روکنے کا ذمہ دار ہے۔ یہ ابتدائی ترتیب کی پرانی پیچیدگیوں کو کسی ایسی چیز سے بدل دیتا ہے جس کا کمپیوٹر خود اندازہ لگا سکتا ہے، جو کہ "اس کال کی ترتیب کو تبدیل نہ کریں” کے بیان سے زیادہ صحت مند انتظام ہے۔

ماڈیول ذیلی کلاس Moduleجامد اعلان ModuleFactoryطریقوں کا ایک چھوٹا سا سیٹ لاگو کرتا ہے۔ سب سے اہم بات یہ ہے۔ ListDependencies، Startاور Stop.

class ExampleModule : public bluetooth::Module {
 public:
  static const ModuleFactory Factory;

 protected:
  void ListDependencies(ModuleList* list) const override {
    list->add<:hcilayer>();
    list->add<:controller>();
  }

  void Start() override {
    hci_layer_ = GetDependency<:hcilayer>();
    controller_ = GetDependency<:controller>();
    // Module is now ready to do work on GetHandler().
  }

  void Stop() override {
    // Release references; the registry stops dependencies after this.
    hci_layer_ = nullptr;
    controller_ = nullptr;
  }

  std::string ToString() const override { return "ExampleModule"; }

 private:
  hci::HciLayer* hci_layer_ = nullptr;
  hci::Controller* controller_ = nullptr;
};

const ModuleFactory ExampleModule::Factory =
    ModuleFactory([]() { return new ExampleModule(); });

یہ کلاس ماڈیول کے پورے معاہدے کی نمائندگی کرتی ہے۔

ListDependencies تخلیق کے وقت، آپ ان ماڈیولز میں ان کی قسمیں شامل کرکے کسی دوسرے ماڈیول کا اعلان کرتے ہیں جن کی آپ کو ضرورت ہے۔ ModuleList. رجسٹری ان ڈیکلریشنز کو تمام ماڈیولز سے پڑھتی ہے اور ان کے سٹارٹ اپ آرڈر کو حسب ذیل شمار کرتی ہے: HciLayer اور Controller میں اس سے پہلے بھاگ رہا ہوں۔ ExampleModule::Start مجھے بلایا گیا ہے۔

اندر Start، GetDependency() فیکٹری ہر انحصار کی پہلے سے چل رہی مثالوں کو دیکھتی ہے اور انہیں خام اشارے کے طور پر واپس کرتی ہے۔ رجسٹری اس بات کو یقینی بناتی ہے کہ انحصار ان کی زندگی بھر رہے، اس لیے ماڈیول ان کو کیش کرتے ہیں۔ Stop انحصار کو روکنے سے پہلے شٹ ڈاؤن کے دوران چلتا ہے، ماڈیولز کو مفت حوالہ جات اور کسی بھی بقایا کام کو منسوخ کرنے کا موقع فراہم کرتا ہے۔

ToString لاگنگ اور ڈمپسی ماڈیولز کے نام فراہم کریں۔ خاموشی Factory یہ ایک چھوٹی چیز ہے جو لیمبڈاس کو رکھتی ہے جو ماڈیول بناتی ہے، اور وہ ہینڈل ہے جسے رجسٹری ماڈیول کو فوری بنانے اور انحصاری گراف میں اس کی شناخت کرنے کے لیے استعمال کرتی ہے۔ چونکہ کوئی ترتیب بالکل استعمال نہیں ہوتی ہے، اس لیے کوئی عالمی ابتدائی ترتیب نہیں ہے جو غلط ہو سکے۔

رجسٹری خود انہیں بوٹ کے وقت ایک ساتھ جوڑتی ہے اور ہر ماڈیول کو ایک ہینڈلر تفویض کرتی ہے۔

ModuleList modules;
modules.add();

// The registry topologically sorts and starts everything.
ModuleRegistry registry;
registry.Start(&modules, thread);

// ... stack runs ...

registry.StopAll();

یہاں، ایپلیکیشن مطلوبہ ٹاپ لیول ماڈیول کا اعلان کرتی ہے اور اسے اس تھریڈ کے ساتھ رجسٹری میں بھیجتی ہے جس پر ماڈیول چلے گا۔

Start انحصاری گراف کے ذریعے چلیں، ہر ماڈیول کو ٹاپولوجیکل ترتیب میں بالکل ایک بار شروع کریں، انحصار انجیکشن کریں، اور Handler فراہم کردہ دھاگے سے منسلک ہوتا ہے۔

StopAll عمل کو ریورس میں بلا کر Stop یہ سٹارٹ اپ آرڈر کے بالکل برعکس ہے، اس لیے کسی ماڈیول کو اس پر منحصر ماڈیولز سے پہلے الگ نہیں کیا جائے گا۔

کیونکہ انحصار لازمی کوڈ کے بجائے واضح ڈیٹا ہیں، وہی مشین TestModuleRegistry یہ ٹیسٹوں کو جعلی انحصار کے اوپر ایک واحد اصلی ماڈیول شروع کرنے کی اجازت دیتا ہے۔

وہ ٹیسٹ ویرینٹ اس ڈیزائن کی خاموش سپر پاور ہے۔ کیونکہ ایک ماڈیول صرف اس کے ذریعے انحصار تک پہنچتا ہے۔ GetDependency()ٹیسٹ جعلی رجسٹر کرسکتے ہیں۔ HciLayer ماڈیول ٹیسٹ کے تحت اصل ماڈیول شروع کرنے سے پہلے فرق نہیں بتا سکتا۔

TestModuleRegistry test_registry;
test_registry.InjectTestModule(&HciLayer::Factory, fake_hci_layer_);
test_registry.Start(&test_registry.GetTestModuleList());

// Drive the fake HCI layer, assert on what ExampleModule does in response.

اس ٹیسٹ سیٹ اپ میں: InjectTestModule اصلی نفاذ کے خلاف جعلی نفاذ کو پہلے سے رجسٹر کریں۔ HciLayer یہ ایک فیکٹری کلید ہے، اس لیے تمام ماڈیولز جو اس پر منحصر ہوں گے۔ HciLayer ہم شفاف طریقے سے جعلی وصول کرتے ہیں۔ Start پھر اس کے اوپر ٹیسٹ کیے جانے والے اصل ماڈیول کو لوڈ کریں۔ اب آپ کے ٹیسٹ واقعات کو جعلی HCI پرت میں فیڈ کر سکتے ہیں اور کمانڈز پر زور دے سکتے ہیں۔ ExampleModule کوئی ہارڈ ویئر یا دیگر پرتیں شامل نہیں ہیں اور ہر چیز کو کنٹرول شدہ دھاگے پر بھیجا جاتا ہے۔

یہ ایماندارانہ معنوں میں یونٹ ٹیسٹنگ ہے، جس میں یونٹس دراصل الگ تھلگ ہیں، جو کہ پچھلے اسٹیکوں میں تقریباً ناممکن تھا۔

اسٹیک بوٹسٹریپ

آپ کو رجسٹری کو ترتیب دینے، تھریڈز کو منتخب کرنے، اعلیٰ سطح کے ماڈیولز شامل کرنے اور ہر چیز کو نافذ کرنے کی ضرورت ہے۔ وہ کچھ Stack اعتراض یہ ایک واحد اندراج نقطہ ہے جو مالک ہے: ModuleRegistry یہ بنیادی دھاگہ ہے اور جب یہ فیصلہ کرتا ہے کہ بلوٹوتھ کو آن کرنا چاہیے تو اینڈرائیڈ شیم لیئر سے بات کرتا ہے۔

بوٹسٹریپ ترتیب میں تین کام انجام دیتا ہے: وہ Thread اسٹیک چلانے کے لیے مناسب ترجیح پر ہے۔ کہ ModuleList موجودہ کنفیگریشن کے لیے ایک اعلیٰ سطحی ماڈیول شامل ہے، خود بخود تمام عبوری انحصار درآمد کرتا ہے۔ اس کے بعد یہ اس تھریڈ کے لیے رجسٹری شروع کرتا ہے اور اس وقت تک بلاک کرتا ہے جب تک کہ تمام ماڈیولز شروع نہ ہو جائیں، اس لیے کال واپس آنے پر اسٹیک مکمل طور پر کام کر سکتا ہے۔

خاتمہ ایک آئینہ دار تصویر ہے۔ رجسٹری تمام ماڈیولز کو ریورس انحصاری ترتیب میں روکتی ہے اور پھر تھریڈ میں شامل ہو جاتی ہے۔

اسے ایک چیز پر مرکوز کرنے کا مطلب یہ ہے کہ بالکل ایک جگہ ہے جو جانتا ہے کہ اسٹیک کیسے اوپر اور نیچے جاتا ہے، بجائے اس کے کہ اس تاریخی صورت حال کی جہاں آغاز بہت سی فائلوں کی ایک ابھرتی ہوئی خاصیت تھی جو امید ہے کہ صحیح ترتیب میں شامل کی گئی تھیں۔

قطار خلاصہ

ماڈیول جو پیکٹ اسٹریمز کو تیار اور استعمال کرتے ہیں وہ ایک دوسرے کو براہ راست کال نہیں کرتے ہیں۔ وہ اس کے ذریعے منسلک ہیں: Queueری ایکٹر پر بنایا گیا ایک رد عمل، محدود، واحد پروڈیوسر، واحد صارف چینل ہے۔ مثال کے طور پر، کس طرح ACL ڈیٹا L2CAP اور HCI تہوں کے درمیان بغیر کسی بلاک یا شیئرنگ کے بہتا ہے۔

قطار دو آدھے انٹرفیس کو بے نقاب کرتی ہے، ہر ایک سرے پر ایک۔ پروڈیوسر سائیڈ ایک کال بیک کو رجسٹر کرکے قطار میں اضافے کو لاگو کرتا ہے جسے جگہ ہونے پر قطار کال کرتی ہے۔ صارف کی طرف ایک کال بیک رجسٹر کرتا ہے جسے ڈیٹا دستیاب ہونے پر قطار کال کرتی ہے۔ کوئی بھی انتظار میں مصروف نہیں ہے اور کوئی آپ کو مکمل یا خالی قطاروں سے نہیں روکتا ہے۔

// Producer side: register to be asked for the next packet.
queue_end_->RegisterEnqueue(
    handler_,
    common::Bind(&MyModule::OnQueueReadyToSend, common::Unretained(this)));

std::unique_ptr<:basepacketbuilder> MyModule::OnQueueReadyToSend() {
  if (pending_packets_.empty()) {
    queue_end_->UnregisterEnqueue();  // Nothing to send; stop being asked.
    return nullptr;
  }
  auto packet = std::move(pending_packets_.front());
  pending_packets_.pop();
  return packet;
}

یہ پیٹرن کا نصف قطار ہے اور اس کے برعکس جس کی زیادہ تر لوگ توقع کریں گے۔ کوئی ڈیٹا قطار میں نہیں ڈالا جاتا۔ اس کے بجائے آپ کال کریں RegisterEnqueue اگر آپ کال بیک استعمال کرتے ہیں اور قطار میں گنجائش ہے، تو آپ اگلی آئٹم کی درخواست کرنے کے لیے کال بیک کو کال کرتے ہیں۔

کال بیک ایک پیکٹ بلڈر یا واپس کرتا ہے۔ nullptr کال کے بعد UnregisterEnqueue جب بھیجنے کے لیے کچھ نہیں بچا۔ یہ موڑ جان بوجھ کر ہے۔ اس کا مطلب یہ ہے کہ بیک پریشر خود بخود ہوتا ہے۔ اگر ڈاؤن اسٹریم صارف سست ہے اور قطار بھری ہوئی ہے، تو کال بیک کال رک جائے گی اور پیکٹ ان کے اپنے بفر میں ڈھیر ہو جائیں گے جہاں وہ چھپے ہوئے کرنل بفر میں نہیں بلکہ ان کے اپنے بفر میں ڈھیر ہو جائیں گے جہاں وہ ناقابل بیان تاخیر کے ساتھ بدل جاتے ہیں۔

Dequeue نصف اس کی بالکل عکاسی کرتا ہے۔

// Consumer side: register to be told when a packet arrives.
queue_end_->RegisterDequeue(
    handler_,
    common::Bind(&MyModule::OnPacketReceived, common::Unretained(this)));

void MyModule::OnPacketReceived() {
  auto packet = queue_end_->TryDequeue();
  if (packet == nullptr) {
    return;
  }
  // Process the received packet on handler_'s thread.
}

کھپت کے لحاظ سے RegisterDequeue ہینڈلر کے پابند کال بیک کو قطار میں منتقل کرتا ہے۔ جب ایک پیکٹ دستیاب ہو جاتا ہے، تو قطار متعلقہ کال بیک ہینڈلر تھریڈ پر پوسٹ کرتی ہے اور اسے اپنے اندر سے کال کرتی ہے۔ TryDequeue پیکٹ تلاش کریں۔ کہ TryDequeue میں اب بھی واپس آ سکتا ہوں۔ nullptr چیک کریں کہ آیا اس شے کی پہلے ہی تصویر لی گئی ہے۔ ایک ہی تھریڈ سے وابستگی کے اصول لاگو ہوتے ہیں۔ کال بیک اس میں انجام دیا جاتا ہے: handler_چونکہ یہ ایک دھاگہ ہے، اس لیے پیکٹ پروسیسنگ سیریل ہے اور کوئی لاکنگ نہیں ہے۔

اندرونی طور پر دونوں سرے ایونٹ ایف ڈی میں بنائے گئے چھوٹے رد عمل والے سیمفورس کے ذریعے مربوط ہوتے ہیں۔ یہ ایک تھریڈ میں پروڈیوسرز کو دوسرے تھریڈ میں صارفین کو محفوظ طریقے سے سگنل دینے کی اجازت دیتا ہے۔ جب دھکیلنے کے لیے بہت ساری چیزیں ہوں، EnqueueBuffer آپ آئٹمز شامل کرنے کے لیے اس پورے ڈانس کو لپیٹ سکتے ہیں اور بفر کو رجسٹریشن کروا سکتے ہیں۔ یہ وہی ہے جو سب سے زیادہ کال سائٹس اصل میں استعمال کرتے ہیں.

HCI پرت

میزبان کنٹرولر انٹرفیس میزبان اسٹیک اور کنٹرولر چپ کے درمیان ایک پروٹوکول ہے۔ گیبلڈورشے میں HciLayer ماڈیول کمانڈ چینل کا مالک ہے اور ایک قاعدہ نافذ کرتا ہے جو کسی بھی سادہ عمل کو ناکام بناتا ہے۔ کریڈٹس کی تعداد کنٹرولر کو بتاتی ہے کہ وہ ایک وقت میں کتنے کمانڈز کو قبول کر سکتا ہے۔ اگر آپ اپنے کریڈٹ سے زیادہ بھیجتے ہیں، تو ایسے طریقوں سے مسائل پیدا ہوتے ہیں جن کا پتہ لگانا مشکل ہوتا ہے۔

یہ براہ راست HCI بائٹس نہیں لکھتا ہے۔ یہ ان پٹ کمانڈ پیکٹ بناتا ہے، اسے HCI پرت تک پہنچاتا ہے، اور حتمی جواب کے لیے کال بیک فراہم کرتا ہے۔ یہ پرت بہاؤ کنٹرول کو سنبھالتی ہے، ان کمانڈز کے جوابات جو انہیں متحرک کرتی ہے، اور غیر منقولہ واقعات کو سبسکرائبرز تک پہنچاتی ہے۔

hci_layer_->EnqueueCommand(
    hci::ResetBuilder::Create(),
    GetHandler()->BindOnceOn(this, &MyModule::OnResetComplete));

void MyModule::OnResetComplete(hci::CommandCompleteView view) {
  auto reset_view = hci::ResetCompleteView::Create(view);
  ASSERT(reset_view.IsValid());
  if (reset_view.GetStatus() != hci::ErrorCode::SUCCESS) {
    LOG_ERROR("Reset failed");
  }
}

پھر ایک HCI ری سیٹ کمانڈ بھیجی جاتی ہے اور تکمیل پر کارروائی کی جاتی ہے۔ hci::ResetBuilder::Create() چونکہ یہ ٹائپ شدہ اور تصدیق شدہ کمانڈ پیکٹ بناتا ہے، اس لیے یہ جسمانی طور پر ایک خراب ری سیٹ نہیں بھیج سکتا۔

EnqueueCommand پرت کی اندرونی کمانڈ کی قطار میں کمانڈ رکھتا ہے۔ یہ قطار کنٹرولر کو صرف اس وقت کمانڈ جاری کرتی ہے جب کریڈٹ دستیاب ہوں اور جب تک کنٹرولر جواب میں کریڈٹ واپس نہ کر دے باقی کو روکے۔ دوسری دلیل ہینڈلر کے لئے پابند کال بیک ہے۔ BindOnceOnتو تکمیل ایک دھاگے میں کی جاتی ہے۔

کمانڈز دو شکلوں میں آتے ہیں: کمانڈ کی تکمیل کے واقعات کے ذریعہ جواب دیا جاتا ہے اور کمانڈ اسٹیٹس کے واقعات کے ذریعہ جواب دیا جاتا ہے۔ EnqueueCommand ایک اوورلوڈ جو ہر ایک کو کال بیک کی درست قسم کی طرف لے جاتا ہے۔

جب کوئی جواب آتا ہے، تو ایک عمومی منظر حاصل کریں اور اسے مخصوص اشیاء تک محدود کریں۔ ResetCompleteViewچیک کریں IsValid() قطعہ پڑھنے سے پہلے۔ وہ توثیق رسم نہیں ہے۔ ایک تجزیہ کار جو آپ کو بتاتا ہے کہ آیا بائٹس درحقیقت متوقع ساخت سے مماثل ہیں۔

HCI پرت آؤٹ پٹ کو ایک اوورلوڈ کال بیک کی بجائے الگ الگ اسٹریمز میں تقسیم کرتی ہے۔ کمانڈ کے جوابات اس کال بیک پر جاتے ہیں جو آپ کمانڈ کے ساتھ فراہم کرتے ہیں۔ ناپسندیدہ واقعات، جیسے کہ ریموٹ ڈیوائس کنکشن، ان ماڈیولز کو بھیجے جاتے ہیں جنہوں نے اس مخصوص ایونٹ کوڈ کے لیے ہینڈلر کو رجسٹر کیا ہے۔ LE میٹا ایونٹس جو کہ ایک ہی LE میٹا ایونٹ آپکوڈ کے چائلڈ ایونٹس ہیں ان کے اپنے سبسکرائبرز کے لیے ملٹی پلیکس کیے جاتے ہیں۔ اور پرتیں تنگ ذیلی انٹرفیس کو بے نقاب کرتی ہیں، جیسے کہ سیکیورٹی انٹرفیس اور LE ایڈورٹائزنگ انٹرفیس، اس لیے ماڈیول صرف HCI کے وہ ٹکڑے دیکھتے ہیں جن کی وہ اصل میں پرواہ کرتے ہیں، نہ کہ پورے فائر ہوز کے۔

یہ علیحدگی درخواست کے جواب کی منطق کو بے ساختہ واقعہ کی منطق سے دور رکھتی ہے۔ پرانے اسٹیک میں، یہ اکثر ایک ہی فنکشن تھا جو ایک ساتھ تین کام کرنے کی کوشش کرتا تھا۔

پاس بیٹھو HciLayer چاندی Controller ماڈیول ایک بار شروع ہونے پر چپ کی جانچ پڑتال اور جواب کو کیش کرنے کا ذمہ دار ہے۔ یہ ریڈ-لوکل-ورژن، ریڈ-لوکل-سپورٹڈ-کمانڈز، ریڈ-بفر-سائز اور LE فنکشن کمانڈز کو انجام دیتا ہے اور پھر ایک سادہ گیٹر کے ذریعے نتائج کو ظاہر کرتا ہے۔

یہ ضروری ہے کیونکہ باقی اسٹیک کو ACL بفر سائز، کنٹرولر کتنے پیکٹ رکھ سکتا ہے، وغیرہ کو مسلسل جاننے کی ضرورت ہے، اور اسے ایک بار درخواست کرنا اور اسے کیش کرنا چپ سے بار بار پوچھنے سے کہیں بہتر ہے۔ جب آپ یہ جاننا چاہتے ہیں کہ آیا کسی خصوصیت کو اونچی پرت میں سپورٹ کیا جاتا ہے۔ Controllerیہ ہارڈ ویئر نہیں ہے۔

پیکٹ کی تعریف کی زبان

وہ جو خاموشی سے بہترین کام کرتے ہیں وہ یہ ہیں: پرانے اسٹیک میں ایک پیکٹ کو پارس کرنے کا مطلب ہے ہاتھ سے کیلکولیٹ شدہ آفسیٹس پر بائٹس کو پڑھنا، انہیں ہاتھ سے منتقل کرنا اور ماسک کرنا، اور امید کرنا کہ ہر مصنف نے آخرت اور حدود کو درست طریقے سے چیک کیا ہے۔ یہ ہمیشہ صحیح نتائج حاصل نہیں کرتا ہے، اور خراب شکل والے بلوٹوتھ پیکٹ جو دلکش ناموں اور لوگو کے ساتھ ختم ہوتے ہیں ایک کلاسک ریموٹ اٹیک سطح ہیں۔

Gabeldorsche ان سب کو Packet Definition Language (PDL) سے بدل دیتا ہے۔ تار کی شکل ایک بار میں بیان کی گئی ہے۔ .pdl فائلیں اور جنریٹرز bluetooth_packetgen C++ پارسر اور بلڈر کلاسز برآمد کرتا ہے۔

ایک PDL فائل ایک اختتامی اعلان کے ساتھ شروع ہوتی ہے اور پھر گنتی، ڈھانچے اور پیکٹ کی وضاحت کرتی ہے۔ مندرجہ ذیل جدول فیلڈ کنفیگریشنز کا خلاصہ کرتا ہے جو آپ اکثر دیکھیں گے۔

تعمیر معنی
field : N N بٹ چوڑائی کا اسکیلر فیلڈ
field : Enum وہ فیلڈز جو کسی نامزد گنتی سے قدریں لیتے ہیں۔
field : N[] N-bit عناصر کی متغیر لمبائی کی صف
field : N[k] k عناصر کی مقررہ صف (ہر ایک N بٹس)
_size_(field) ایک فیلڈ جو دوسرے فیلڈ کا بائٹ سائز رکھتا ہے۔
_count_(field) ایک فیلڈ جو ایک صف میں عناصر کی تعداد رکھتی ہے۔
_payload_ متغیر باڈی پیرنٹ پیکٹ کے ذریعہ لے جایا جاتا ہے۔
_fixed_ = value مستقل فیلڈز جو بلڈر ہمیشہ خارج کرتا ہے۔
_reserved_ بٹ جو تار پر ہمیشہ 0 ہوتا ہے۔

یہ جدول PDL فائلوں کی ذخیرہ الفاظ کا احاطہ کرتا ہے۔ اسکیلر اور گنتی کے فیلڈز قطعی بٹ چوڑائی کے ساتھ انفرادی اقدار کی وضاحت کرتے ہیں، لہذا 3 بٹ فیلڈ دراصل 3 بٹس ہوتی ہے اور جنریٹر اس کے مطابق اسے کمپریس کرتا ہے۔ صف کی شکلیں متغیر یا مقررہ لمبائی کے بار بار ڈیٹا کو بیان کرتی ہیں۔

کہ _size_ اور _count_ میدان ہوشیار حصہ ہے. قسم کو ایک بار لمبائی کا سابقہ ​​بیان کرنے دیں، اور جنریٹر کی زنجیر کو ریاضی میں رکھیں تاکہ بلڈر سیریلائزیشن کے وقت لمبائی کی گنتی کرے اور تجزیہ پارس کے وقت اس کی تصدیق کرے۔

کہ _payload_ مارکر یہ ہیں کہ پیکٹ کی وراثت والدین کے اندر بچے کے جسم کو کیسے منتقل کرتی ہے۔ اور _fixed_ اور _reserved_ مستقل اور مطلوبہ صفر کو انکوڈ کریں تاکہ انسانوں کو انہیں یاد رکھنے کی ضرورت نہ ہو۔

یہاں سب کچھ اعلانیہ ہے اور جنریٹر اسے کوڈ میں تبدیل کرتا ہے جسے آپ حدود کی جانچ کے بارے میں نہیں بھول سکتے۔

ایک ٹھوس تعریف واضح چوڑائی اور رکاوٹوں کے ساتھ ساخت کی وضاحت کی طرح دکھائی دیتی ہے۔ یہاں نچلا پیکٹ اوپری فیلڈ کو محدود کرتا ہے۔

little_endian_packets

enum OpCode : 16 {
  RESET = 0x0C03,
  READ_LOCAL_NAME = 0x0C14,
}

packet Command {
  op_code : OpCode,
  _size_(payload) : 8,
  _payload_,
}

packet Reset : Command (op_code = RESET) {
}

packet ReadLocalNameComplete : CommandComplete (command_op_code = READ_LOCAL_NAME) {
  status : ErrorCode,
  local_name : 8[248],
}

یہ ایک عام اوپکوڈ شمار کی وضاحت کرتا ہے۔ Command ایک پیرنٹ پیکٹ اور دو کنکریٹ پیکٹ ہیں۔ کہ Command پیکٹ ہے۔ op_codeپے لوڈ کا ایک بائٹ سائز اور خود پے لوڈ، جو تمام ہدایات کے ذریعے مشترکہ شکل ہے۔ Reset سے وراثت Command اور ٹھیک کریں op_code کو RESETتیار کردہ بلڈر ہمیشہ صحیح اوپکوڈ کو خارج کرے گا، اور تیار کردہ منظر اس کی رکاوٹوں کو ملا کر دوبارہ ترتیب کو پہچان سکتا ہے۔ ReadLocalNameComplete سے وراثت CommandComplete اور status شمار شدہ فیلڈز پلس local_name یہ اس طرح لکھا گیا: 8[248]یعنی 8 بٹس کے 248 عناصر، بالکل اسی طرح بلوٹوتھ تفصیلات مقامی نام کے فیلڈ کی وضاحت کرتی ہے۔

قوسین میں موجود رکاوٹیں اس بات کو یقینی بناتی ہیں کہ تیار کردہ کوڈ خام بفر کو صحیح ویو کی قسم کی طرف لے جا سکتا ہے جو آپ کے پاس ہوتا ہے، بغیر کسی ہاتھ سے لکھے گئے سوئچ بیانات کی ضرورت کے۔

اس تعریف کے مطابق، جنریٹر فی پیکٹ دو قسم کی کلاسیں تیار کرتا ہے۔ بلڈر سٹرکچرڈ ڈیٹا کو بائٹس میں سیریلائز کرتا ہے، اور ویو بائٹس کو سٹرکچرڈ رینج چیکنگ ایکسیسرز میں پارس کرتا ہے۔

// Building: structured data becomes validated bytes.
auto builder = hci::ResetBuilder::Create();
std::vector bytes;
BitInserter it(bytes);
builder->Serialize(it);   // writes op_code, computed size, payload

// Parsing: bytes become a validated, typed view.
auto command_view = hci::CommandView::Create(
    PacketView(std::make_shared<:vector>>(bytes)));
auto name_view = hci::ReadLocalNameCompleteView::Create(command_view);

if (name_view.IsValid()) {
  std::array name = name_view.GetLocalName();
}

عمارت کے پہلو سے پتہ چلتا ہے کہ بلڈر کو صحیح ترتیب معلوم تھی۔ Serialize کھیتوں میں ترتیب سے چلیں۔ BitInserterیہ فکسڈ اوپکوڈز لکھتا ہے، پے لوڈ کے سائز کا حساب لگاتا اور لکھتا ہے، اور پے لوڈ کو خارج کرتا ہے، اس لیے یہ کبھی بھی آفسیٹس کو نہیں چھوتا۔

تجزیہ کا پہلو سست اور تہہ دار ہے۔ کوئی راستہ نہیں PacketView مشترکہ بائٹ بفر کو کاپی کرنے کے بجائے لپیٹ دیتا ہے۔ CommandView عام اور مخصوص کمانڈ ہیڈر کی ترجمانی کرتا ہے۔ ReadLocalNameCompleteView یہ اسے مزید تنگ کرتا ہے۔

اہم طریقہ ہے۔ IsValid()تیار کردہ کوڈ اس بات کو یقینی بنانے کے لیے لاگو ہوتا ہے کہ بفر تمام شعبوں کے لیے کافی لمبا ہے اور صارف کے کچھ پڑھنے سے پہلے تمام رکاوٹوں کو برقرار رکھا جاتا ہے۔ وہ تصدیق کے بعد ہی کال کرتے ہیں۔ GetLocalName() تجزیہ شدہ فیلڈز کو ٹائپ کردہ صف کے بطور حاصل کریں۔

چونکہ تجزیہ کار ہر پیکٹ کے لیے ایک ہی تصریحات کے ساتھ تیار کیا جاتا ہے، اس لیے اب دستی طور پر کیڑے کی پوری کیٹیگریز کو لکھنا ممکن نہیں ہے جو ایک ایک کر کے باہر جاتے ہیں یا حد سے باہر جاتے ہیں۔ اسی PDL سے پیدا ہونے والی Python بائنڈنگز بھی ہیں، اس لیے ٹیسٹ سویٹ اسی منطق کے ساتھ، بائٹ بائی بائٹ، پروڈکشن اسٹیک کی طرح پارس اور پیکٹ بناتا ہے۔

ACL مینیجر اور کنکشن مینجمنٹ

خام HCI پرت کے اوپر AclManagerایک ماڈیول غیر مطابقت پذیر کنکشن پر مبنی لنکس کے لیے ذمہ دار ہے جو آلہ کے منسلک ہونے کے بعد صارف کا اصل ڈیٹا لے جاتا ہے۔ یہ کلاسک اور کم توانائی والے دونوں کنکشنز کا انتظام کرتا ہے، کنکشن کو ان کے اپنے کال بیک انٹرفیس کے ساتھ اشیاء کے طور پر پیش کرتا ہے بجائے اس کے کہ غلط استعمال ہونے کے انتظار میں عالمی ٹیبل کے گرد تیرتے ہوئے انٹیجر ہینڈلز کے طور پر۔ اندرونی طور پر، اسے کلاسک اور LE نفاذ میں تقسیم کیا گیا ہے، جو اگلے حصے میں بیان کردہ راؤنڈ رابن ڈیٹا شیڈیولر کا اشتراک کرتے ہیں۔

جب آپ کنکشن کی درخواست کرتے ہیں، تو آپ ایک کال بیک رجسٹر کرتے ہیں جو کنکشن کے کامیاب یا ناکام ہونے پر چلتا ہے، اور اگر کامیاب ہوتا ہے، تو آپ کو ایک کنکشن آبجیکٹ موصول ہوتا ہے جو اس لنک کے لیے قطار کا مالک ہوتا ہے۔

// Register interest in Classic connection events, then connect.
acl_manager_->RegisterCallbacks(this, GetHandler());
acl_manager_->CreateConnection(remote_address);

void MyModule::OnConnectSuccess(
    std::unique_ptr<:acl_manager::classicaclconnection> connection) {
  uint16_t handle = connection->GetHandle();
  // The connection object owns the data queue for this link.
  connection->GetAclQueueEnd()->RegisterDequeue(
      GetHandler(),
      common::Bind(&MyModule::OnAclData, common::Unretained(this)));
  connections_[handle] = std::move(connection);
}

یہ صارف کے نقطہ نظر سے کنکشن لائف سائیکل کو ظاہر کرتا ہے۔ RegisterCallbacks ہینڈلر تھریڈ پر کنکشن ایونٹ میں ماڈیول کو سبسکرائب کریں اور CreateConnection کسی صفحہ پر نیویگیٹ کرنے اور ریموٹ ایڈریس سے جڑنے کے لیے HCI ڈانس شروع کریں۔

اگر کامیاب ہو، OnConnectSuccess وصول کریں ClassicAclConnection وہ شخص جو مالک ہے unique_ptrاس کا مطلب یہ ہے کہ ملکیت واضح ہے اور جب اعتراض کو حذف کر دیا جاتا ہے تو لنک قطعی طور پر ٹوٹ جاتا ہے۔ کنکشن کی اپنی ڈیٹا کی قطار ہوتی ہے، جس تک اس کے ذریعے رسائی حاصل کی جاتی ہے: GetAclQueueEnd()پہلے کی طرح بالکل اسی قطار کے پیٹرن کا استعمال کرتے ہوئے ڈیکیو کال بیک کو رجسٹر کریں۔

آخر میں، ہم کنکشن کو اس کے ہینڈل سے کلید والے نقشے پر منتقل کرتے ہیں۔ اس بارے میں کوئی ابہام نہیں ہے کہ لنکس کا مالک کون ہے، جو پچھلے ڈیزائن میں استعمال اور مفت کیڑے کا ایک حقیقی ذریعہ تھا۔

LE سائیڈ قابل ذکر ایک شیکن کا اضافہ کرتی ہے۔ یہ رازداری ہے۔ ایل ای ڈیوائسز قابل حل نجی پتوں کا استعمال کرتے ہوئے اشتہاری پتوں کو گھماتے ہیں، جس سے ٹریکرز کے لیے اپنے میک کے ذریعے ڈیوائس کی پیروی کرنا ناممکن ہو جاتا ہے۔

گیبلڈورشے LeAddressManager یہ کنٹرولر کی مقامی ایڈریس روٹیشن اور ریزولیوشن لسٹ کا مالک ہے اور ایڈریسز تبدیل ہونے پر کوآرڈینیٹ کرتا ہے، ایڈریس کے استحکام پر انحصار کرنے والے آپریشنز کے درمیان گردش کو روکتا ہے۔ یہ ایک مشکل، وقت کے لحاظ سے حساس کام ہے، اور پورے LE کوڈ میں ایڈریسنگ منطق کو پھیلانے کے بجائے اسے ایک جزو پر مرکوز کرنا بالکل اسی قسم کا فیصلہ ہے جس کو آسان بنانے کے لیے مجموعی فن تعمیر کو بنایا گیا ہے۔

راؤنڈ رابن شیڈیولر اور ACL ڈیٹا پاتھ

کنٹرولر کے پاس ACL بفرز کی ایک محدود تعداد ہے، اور تمام کنکشنز ان کے لیے مقابلہ کرتے ہیں۔ اگر ایک مصروف کنکشن کو اس کے تمام بفر استعمال کرنے کی اجازت ہے، تو دوسرے کنکشن لٹک جائیں گے۔ اس کا مطلب ہے کہ آپ کے فون پر فائل کی منتقلی آڈیو کو گھٹا رہی ہے۔

Gabeldorsche اس مسئلے کو اس طرح حل کرتا ہے: RoundRobinScheduler یہ فی کنکشن قطار اور کنٹرولر سے ایک مشترکہ لنک کے درمیان بیٹھتا ہے۔

شیڈیولر کنٹرولر کے پاس موجود بفر کریڈٹس کی تعداد پر نظر رکھتا ہے، اور ہر کنکشن سے ایک وقت میں ایک پیکٹ کو بار بار ڈیکیو کرکے، اسے کنٹرولر کے ACL پیکٹ کے سائز میں تقسیم کرکے، اور اسے نیچے کی طرف بھیج کر کریڈٹس کی تعداد کو کم کرتا ہے۔

جب کنٹرولر رپورٹ کرتا ہے کہ اس نے ایک پیکٹ منتقل کر دیا ہے اور مکمل پیکٹ کاؤنٹ ایونٹ کے ذریعے بفر کو آزاد کر دیا ہے، تو شیڈیولر کریڈٹ واپس جوڑ کر ٹرانسمیشن کو دوبارہ شروع کر دیتا ہے۔

چونکہ کنکشنز کو دوسرے کو چھونے سے پہلے ایک کو آزاد کرنے کے بجائے راؤنڈ رابن ترتیب میں دیکھا جاتا ہے، اس لیے بینڈوتھ کو لنکس کے درمیان منصفانہ طور پر شیئر کیا جاتا ہے بغیر کسی کنکشن کے دوسرے کنکشن کی موجودگی کا علم ہونا۔ فریگمنٹیشن بھی یہاں ہوتا ہے، اس لیے اوپری پرتیں پورے L2CAP فریم کے بارے میں سوچتی ہیں، اور شیڈیولر خاموشی سے اسے کنٹرولر سائز میں کاٹتا ہے اور واپسی پر کریڈٹس کو دوبارہ جوڑتا ہے۔ یہ ایک جزو "میرے ائرفونز اور میری فائلز کی مطابقت پذیری ایک ساتھ رہتے ہیں” اور "ایک کا انتخاب کریں” کے درمیان فرق ہے۔

L2CAP اور ڈیٹا پائپ لائن

لاجیکل لنک کنٹرول اینڈ اڈاپٹیشن پروٹوکول (L2CAP) ملٹی پلیکس ایک واحد ACL لنک کو ایک سے زیادہ منطقی چینلز میں بناتا ہے اور بڑے سروس ڈیٹا اکائیوں کی تقسیم اور دوبارہ اسمبل کو سنبھالتا ہے۔

Gabeldorsche کلاسک اور LE متغیرات کو الگ الگ ماڈیول کے طور پر لاگو کرتا ہے۔ L2capClassicModule اور L2capLeModuleہم ذیل میں مشترکہ مشینیں شیئر کرتے ہیں۔ یہ فکسڈ چینلز کے درمیان فرق کرتا ہے، جو ہمیشہ سگنلنگ جیسے کاموں کے لیے موجود ہوتے ہیں، اور متحرک چینلز، جو مخصوص خدمات کی درخواست پر کھلتے ہیں۔

ایک سروس پروٹوکول یا سروس ملٹی پلیکسر کے ذریعہ خود کو رجسٹر کرتی ہے، اور جب کوئی ریموٹ ڈیوائس اس سروس کے لیے چینل کھولتا ہے، تو سروس کو چینل آبجیکٹ موصول ہوتا ہے جو فی الحال قطار کا مالک ہے۔

// Register a dynamic L2CAP service on a PSM.
dynamic_channel_manager_->RegisterService(
    kMyPsm,
    security_policy,
    GetHandler()->BindOnceOn(this, &MyModule::OnServiceRegistered),
    GetHandler()->BindOn(this, &MyModule::OnConnectionOpen));

void MyModule::OnConnectionOpen(
    std::unique_ptr<:classic::dynamicchannel> channel) {
  channel->RegisterOnCloseCallback(/* ... */);
  channel->GetQueueUpEnd()->RegisterDequeue(/* ... */);
}

یہاں سروس پروٹوکول سروس ملٹی پلیکسر ویلیو کے خلاف رجسٹرڈ ہے، جو کہ پورٹ نمبر کے مطابق L2CAP ہے۔ RegisterService یہ ایک PSM، ایک سیکیورٹی پالیسی کا استعمال کرتا ہے جو چینل کے لیے درکار جوڑی اور خفیہ کاری کی سطح کو بیان کرتی ہے، رجسٹریشن کی تصدیق کے لیے ایک بار کا کال بیک، اور ایک بار بار آنے والی کال بیک جو ہر بار ریموٹ پیئر کے چینل کو کھولنے پر چلتا ہے۔ جب چینل کھلتا ہے۔ OnConnectionOpen وصول کریں DynamicChannel وہ شخص جو مالک ہے unique_ptrاس قطار میں قریبی کال بیک اور ڈیکیو ہٹانے کو منسلک کریں۔

سیکیورٹی پالیسی، جو بعد میں ایڈہاک چیک کے بجائے رجسٹریشن کا پیرامیٹر ہے، اس کا مطلب ہے کہ سروس کی ضرورت سے کم سیکیورٹی کی سطح پر چینل کو جسمانی طور پر نہیں کھولا جا سکتا۔ رن ٹائم پر سوچنے کی بجائے ایک ٹھوس پراپرٹی کے طور پر سیکیورٹی اس اسٹیک میں بار بار چلنے والا ڈیزائن انتخاب ہے۔

مانوس چینل آبجیکٹ کے نیچے ایک حقیقی ڈیٹا پائپ لائن ہے، اور یہ اس بات کی تصویر کشی کے قابل ہے کہ بائٹس اس سے کیسے گزرتی ہیں۔

outgoing SDU
    -> per-channel Segmenter (splits SDU into PDUs, applies mode)
    -> channel Scheduler (picks which channel sends next, by priority)
    -> Fragmenter (splits PDUs to ACL buffer size)
    -> AclManager queue -> RoundRobinScheduler -> controller

incoming from controller
    -> Reassembler (rebuilds PDUs from ACL fragments)
    -> per-channel Recombiner (rebuilds SDUs from PDUs)
    -> channel queue up-end -> upper layer

یہ خاکہ L2CAP ڈیٹا کے بہاؤ کی دو سمتوں کا سراغ لگاتا ہے۔ باہر نکلتے ہوئے، اوپری پرت کے سروس ڈیٹا یونٹس چینل کے سیگمینٹر میں داخل ہوتے ہیں، جو انہیں پروٹوکول ڈیٹا یونٹس میں تقسیم کرتا ہے اور چینل کے ٹرانسمیشن موڈ کو لاگو کرتا ہے، جیسے کہ بنیادی موڈ یا بڑھا ہوا ری ٹرانسمیشن موڈ، تسلیم اور دوبارہ منتقلی کے ذریعے۔

فی لنک شیڈولر پھر طے کرتا ہے کہ کون سا چینل آگے منتقل کرنا ہے چینل کی ترجیح کی بنیاد پر، اس لیے تاخیر سے حساس چینلز کو ترجیح دی جا سکتی ہے۔ فریگمنٹر اس PDU کو کنٹرولر کے ACL بفر کے سائز تک کم کرتا ہے اور اسے کنٹرولر کو منتقل کرتا ہے۔ AclManager پچھلے حصے سے قطاریں اور راؤنڈ رابن شیڈولرز۔

راستے میں، عمل الٹ ترتیب میں چلتا ہے۔ دوبارہ جمع کرنے والا ACL کے ٹکڑوں کو PDU میں دوبارہ جوڑتا ہے، فی چینل Recombiner اصل SDU کو دوبارہ بناتا ہے، اور مکمل شدہ SDU کو چینل کی قطار کے اوپری حصے میں منتقل کیا جاتا ہے جہاں اسے اوپری تہہ پڑھتی ہے۔

چونکہ ہر قدم ایک چھوٹا، قابل امتحان جزو ہے جس میں ایک ذمہ داری ہے، L2CAP، تاریخی طور پر سب سے چھوٹی پرتوں میں سے ایک، کو سنبھالنا آسان ہو جاتا ہے۔

سیکیورٹی اور جوڑا بنانا

کہ SecurityModule دونوں منتقلیوں کے لیے جوڑا بنانے، یکجا کرنے اور خفیہ کاری کو مرکزی بناتا ہے۔ کلاسک جوڑا ایک لنک پر طریقہ کار کو انجام دیتا ہے، جبکہ LE سیکیورٹی مینیجر پروٹوکول SMP کے لیے وقف کردہ ایک مقررہ L2CAP چینل پر عمل کرتا ہے۔

یہ ماڈیول مختلف قسم کے ایسوسی ایٹیو ماڈلز کے لیے ریاستی مشینوں کو چلاتا ہے، بشمول عددی موازنہ، کرپٹوگرافک کینگ، آؤٹ آف بینڈ وغیرہ، اور عام طور پر کام کرتا ہے۔ فریم ورک کال بیک انٹرفیس کے ذریعے صارف کے تعامل کو ظاہر کرتا ہے تاکہ یہ جوڑا بنانے کا ڈائیلاگ دکھا سکے اور صارف نے جو منتخب کیا ہے اس کی اطلاع دے سکے۔

ڈیزائن کا مقصد دوسرے ماڈیولز کو ان کی اپنی خفیہ کاری یا جوڑی کی منطق کو لاگو کرنے سے روکنا ہے۔ اگر L2CAP کو ڈیٹا منتقل کرنے سے پہلے ایک انکرپٹڈ چینل کی ضرورت ہوتی ہے، تو یہ کی اسٹور تک نہیں پہنچتا یا خود انکرپشن کا عمل شروع نہیں کرتا۔ یہ اپنے انفورسمنٹ انٹرفیس کے ذریعے سیکیورٹی ماڈیول سے درخواست کرتا ہے، اپنے ہینڈلر سے نتیجہ کا انتظار کرتا ہے، اور پھر اوپری پرت پر ایک چینل کھولتا ہے۔

یہ وہی اصول ہے جو باقی اسٹیک پر ہوتا ہے، جو انتہائی حساس ریاستوں پر لاگو ہوتا ہے۔ ایک ماڈیول جوڑا بنانے والی ریاستی مشین اور کلید کا مالک ہے، اور باقی سب اس ماڈیول کو پیغامات بھیجتے ہیں۔ ایک آڈٹ شدہ جگہ پر خفیہ کاری کے فیصلوں کو سنٹرلائز کرنا اس تاریخی صورتحال کے مقابلے میں نمایاں طور پر محفوظ ہے جہاں سیکیورٹی سے متعلق چیک متعدد فائلوں میں پھیلے ہوئے تھے اور بعض اوقات صرف ان میں سے ایک میں ہی بھول جاتے ہیں۔

GATT اور ATT

LE پروٹوکول اسٹیک کے اوپری حصے میں انتساب پروٹوکول (ATT) اور Generic Attribute Profile (GATT) ہے، ایک درخواست کے جواب کا ڈیٹا بیس ہے جو عملی طور پر تمام LE مصنوعات استعمال کرتے ہیں۔

ATT کنکشن آپریشنز کی وضاحت کرتا ہے، پڑھنا، لکھنا، مطلع کرنا، اور نمبر والے انتساب ہینڈلز پر ڈسپلے کرتا ہے اور اپنے ہی طے شدہ L2CAP چینل پر چلتا ہے۔ GATT ان خصوصیات کو خدمات اور خصوصیات میں ترتیب دیتا ہے، خلاصہ جو کہ دل کی شرح مانیٹر یا ایئربڈز فون کو فراہم کرتے ہیں۔

Gabeldorsche میں، یہ اس کے نیچے کی ہر چیز پر صاف ستھری تہوں والا ہے۔ ATT فکسڈ چینل L2CAP انٹرفیس کا ایک کلائنٹ ہے، اس لیے یہ چینل پر دوسرے فکسڈ چینلز کی طرح اسی میکانزم کے ذریعے سنتا ہے اور پیکٹوں کو ایک ہی قطار کے پیٹرن کے ذریعے منتقل کرتا ہے۔ ان پیکٹوں کی وضاحت ہر چیز کی طرح PDL میں کی گئی ہے، لہذا ATT درخواستوں کو پڑھتا ہے اور ان کے جوابات بلڈرز اور ویوز کے ساتھ تیار کیے جاتے ہیں جو HCI کمانڈز کی طرح ہی درستگی کی ضمانت دیتے ہیں۔

GATT ATT کے اوپر خدمات اور خصوصیات کا ایک ڈیٹا بیس بناتا ہے اور مندرجہ بالا پروفائلز پر رجسٹریشن اور نوٹیفکیشن انٹرفیس کو ظاہر کرتا ہے۔

فن تعمیر کے فوائد یہاں ٹھیک ٹھیک طریقوں سے ظاہر ہوتے ہیں۔ چونکہ GATT ماڈیولز کا ایک اور مجموعہ ہے جو قطاروں اور آنے والے پیکٹوں کے ذریعے بات کرتا ہے، اس لیے ان کا اصل ہارڈ ویئر کے نیچے کسی پرت کے بغیر روٹ کینال کے ذریعے ورچوئل پیئرز کے خلاف تجربہ کیا جا سکتا ہے۔

پڑوس اور اسٹوریج ماڈیولز

سپورٹ ماڈیولز کے دو گروپ تصویر کو مکمل کرتے ہیں۔ ملحقہ ماڈیول دیگر آلات کی دریافت اور شناخت کو سنبھالتا ہے، اور اسٹوریج ماڈیول استقامت کو سنبھالتا ہے۔

دریافت ایک واحد کام نہیں ہے، بلکہ متعدد کام ہیں، اور گیبلڈورشے ہر کام کو پڑوسی چھتری کے نیچے مرکوز جزو کے طور پر ماڈل کرتا ہے۔ لوک اپ کلاسک ڈیوائس کی دریافت اور دریافت کے طریقوں کو ہینڈل کرتا ہے۔ صفحہ اور صفحہ اسکین ہینڈل قابل رسائی اور مربوط ہو جاتا ہے۔ نام کی قرارداد ریموٹ ڈیوائس کے انسانی پڑھنے کے قابل نام کو بازیافت کرتی ہے۔

اسے ایک بڑے سرچ بلاب کے بجائے تنگ انٹرفیس کے ساتھ انفرادی اجزاء کے طور پر رکھنے کا مطلب یہ ہے کہ ہر جزو کے بارے میں استدلال کیا جا سکتا ہے اور اس کی جانچ کی جا سکتی ہے۔ اس کا مطلب یہ بھی ہے کہ نام کی ریزولوشن میں موجود کیڑے غلطی سے استفسار کی حالت کو خراب نہیں کر سکتے ہیں کیونکہ وہ متغیر حالت کا اشتراک نہیں کرتے ہیں۔

سٹوریج ماڈیول بنیادی طور پر منسلک آلات اور ان کی کلیدوں کو یاد رکھنے کے لیے ذمہ دار ہے، اس کے ساتھ ساتھ اڈاپٹر اور آلے کی خصوصیات ریبوٹس میں۔ یہ ڈیوائس اور اس کی خصوصیات کا ایک ان میموری ماڈل فراہم کرتا ہے، اور بیچ رائٹ کے لیے کنفیگریشن فائل میں اس ماڈل کو برقرار رکھتا ہے، جس سے ڈسک کی بدعنوانی کو پراپرٹی کی تبدیلیوں کے پھٹنے سے روکتا ہے۔

چونکہ مستقل مزاجی ماڈیول انٹرفیس کے پیچھے ہے، باقی اسٹیک کنفیگریشن فائلوں کو براہ راست پارس کرنے کے بجائے میتھڈ کالز کے ذریعے ڈیوائس کی خصوصیات کو پڑھتا اور لکھتا ہے۔ آن ڈسک فارمیٹ تمام پرتوں کو جاننے کی ضرورت کے بغیر تبدیل ہو سکتا ہے۔ جب بھی آپ ریبوٹ کرتے ہیں ایک جوڑا آلہ کھونا یاد رکھنا ایک بری چیز ہے، لہذا اس ماڈیول کو خاموشی سے فائل کیا جا سکتا ہے۔

سیون اور اگواڑے کی پرتیں۔

آپ ایک کمٹ میں پورے بلوٹوتھ اسٹیک کو دوبارہ نہیں لکھ سکتے اور بھیج نہیں سکتے۔ گیبلڈورشے کو بتدریج جاری کیا گیا، ایک وقت میں ایک پرت۔ اس کا مطلب ہے کہ طویل عرصے تک نئے GD ماڈیولز کو پرانے فلورائیڈ پروفائل کوڈز کے ساتھ ایک ساتھ رہنا پڑے گا۔

کہ shim/ پرت سفارتی پل کا کام کرتی ہے جو اسے ممکن بناتی ہے۔ یہ پرانے سی طرز کے انٹرفیس کو بے نقاب کرتا ہے جس کی BTIF اور BTA توقع کرتے ہیں، اور ان کالز کو نیچے نئے ماڈیول میتھڈ کالز میں ترجمہ کرتا ہے، جو کہ جب وہ حد عبور کرتے ہیں تو GD اسٹیک تھریڈ پر جاتے ہیں۔ انفرادی تہوں کو جھنڈوں کے پیچھے گیٹ کیا گیا تھا تاکہ مخصوص پرتوں کو ایک بلڈ میں جی ڈی اور دوسری بلڈ میں میراثی کوڈ پر چلنے دیا جائے۔ اس نے ہمیں غیر آثار قدیمہ کے جھنڈے کو تبدیل کرکے رجعت کو ایک مخصوص پرت میں دو طرفہ کرنے کی اجازت دی۔

کہ facade/ پرتیں بالکل مختلف مقصد کی تکمیل کرتی ہیں۔ ہر GD ماڈیول ایک gRPC سروس کو سامنے لا سکتا ہے جسے Facade کہا جاتا ہے، جو بیرونی عمل کو ماڈیول کو براہ راست چلانے کی اجازت دیتا ہے۔ یہ وہ سیون ہے جہاں ٹیسٹنگ فریم ورک جوڑتا ہے۔ پورے اینڈرائیڈ فریم ورک کے ذریعے جانچ کرنے کے بجائے، ٹیسٹ ایک ہی ماڈیول کو لانچ کر سکتے ہیں، gRPC کے ذریعے اس کے اگواڑے سے جڑ سکتے ہیں، اور اس کے ایونٹ اسٹریم کا مشاہدہ کرتے ہوئے براہ راست ٹیسٹ کے تحت پرت پر کمانڈز کو چلا سکتے ہیں۔

service HciLayerFacade {
  rpc SendCommand(Command) returns (google.protobuf.Empty) {}
  rpc StreamEvents(google.protobuf.Empty) returns (stream Event) {}
}

یہ ایک خاکہ ہے کہ ماڈیول پروٹوبف میں کیسا لگتا ہے۔ ایک gRPC سروس کی وضاحت ایک انری طریقہ کے ساتھ کریں جو ماڈیول کو کمانڈ بھیجتا ہے اور ایک سرور اسٹریمنگ طریقہ جو ماڈیول کے ذریعے خارج ہونے والے کسی بھی ایونٹ کو کال کرنے والے کو واپس دھکیلتا ہے۔ چونکہ یہ gRPC ہے، ٹیسٹ چلانے والے کلائنٹ کو کسی بھی زبان میں لکھا جا سکتا ہے، اور پروجیکٹ نے Python کو اس کی پڑھنے کی اہلیت اور ٹیسٹ لکھنے کی رفتار کے لیے منتخب کیا۔

اگواڑا ہر اندرونی پرت کو کسی ایسی چیز میں بدل دیتا ہے جسے عمل کے باہر سے دیکھا جا سکتا ہے بغیر اس کے ارد گرد ایک پورا آپریٹنگ سسٹم بنائے۔ یہ ایک قابل آزمائش ڈیزائن اور ایک ڈیزائن کے درمیان فرق ہے جس کے ڈیزائن دستاویز میں صرف قابل امتحان لفظ ہے۔

بلڈنگ سسٹم انضمام

اس میں سے کوئی بھی تعمیراتی آلے کے بغیر لطف اندوز نہیں ہوگا جو پیکٹ جنریٹر کے ساتھ فرسٹ کلاس شہری کی طرح برتاؤ کرتا ہے۔ گیبلڈورسے سونگ کا استعمال کرتے ہوئے بنایا گیا ہے، مخصوص فائل ناموں کے ساتھ AOSP بلڈ سسٹم۔ Android.bpPDL جنریٹر صارف کے بیان کردہ اصولوں کے ساتھ وائرڈ ہے، لہذا .pdl فائلیں خود بخود C++ ہیڈر میں بلڈ گراف کے حصے کے طور پر مرتب ہو جاتی ہیں۔

میکانزم ماخذ کے اصول بنائے جاتے ہیں۔ تعمیر کے قوانین ہیں bluetooth_packetgen اوزار کا استعمال کرتے ہوئے .pdl درج کریں اور اعلان کریں۔ .h طاقت کی پیداوار. ایک C++ لائبریری جو کہ ماخذ کے طور پر تیار کردہ ہیڈرز کی فہرست بناتی ہے وہ ہیڈرز کو ڈیمانڈ پر بنائے گی اور جب بھی ضروری ہو انہیں دوبارہ تعمیر کرے گی۔ .pdl فائل کو تبدیل کریں۔

اس کا عملی اثر یہ ہے کہ ڈویلپر پیکٹ کی تعریفوں میں ترمیم اور دوبارہ تعمیر کر سکتے ہیں، اور Python بائنڈنگز میں نئی ​​قسم کے بلڈرز اور ویوز موجود ہیں جو C++ اہداف اور متوازی قواعد کے ذریعے جانچ کے لیے استعمال ہوتے ہیں۔ ایسا کوئی چیک ان جنریشن کوڈ نہیں ہے جو مطابقت پذیری سے باہر ہو جائے، اور جائزہ کے لیے تبدیلیاں بھیجنے سے پہلے بھول جانے کے لیے کوئی دستی کوڈ جنریشن کے اقدامات نہیں ہیں۔ معلومات کا ایک ہی ذریعہ پروڈکشن C++ اور ٹیسٹ Python کے لیے فراہم کیا جاتا ہے، اور بلڈ سسٹم اس بات کو یقینی بناتا ہے کہ وہ کبھی بہہ نہ جائیں۔

لاگنگ، میٹرکس، اور ڈمپسی

ایک اسٹیک جو آپ نہیں دیکھ سکتے وہ اسٹیک ہے جسے آپ ڈیبگ نہیں کرسکتے ہیں، اور بلوٹوتھ ڈیبگنگ اکثر بگ رپورٹ کے بعد ہوتی ہے۔

Gabeldorsche اسی کے مطابق سرمایہ کاری کرتا ہے۔ ہم معیاری اینڈرائیڈ لاگنگ میکروز کے ذریعے ماڈیول کے لیے مخصوص ٹیگز استعمال کرتے ہوئے لاگ ان کرتے ہیں، لہذا ہر لاگ لائن ہمیں بتاتی ہے کہ اسے کس پرت نے بنایا ہے۔ ہم سٹرکچرڈ میٹرکس کو بھی برقرار رکھتے ہیں جو پورے بیڑے میں صحت کی مجموعی نگرانی کے لیے پلیٹ فارم کی شماریاتی پائپ لائن فراہم کرتے ہیں۔

سب سے زیادہ مفید ڈیبگنگ افورڈنس ڈمپسی انضمام ہے۔ ایک ماڈیول ڈمپ طریقہ کو نافذ کر سکتا ہے، اور رجسٹری چل رہے ماڈیولز کی چھان بین کر سکتی ہے اور ہر ماڈیول سے اس کی موجودہ حالت کو سیریلائز کرنے کے لیے کہہ سکتی ہے، جسے بگ رپورٹ کے بلوٹوتھ سیکشن میں شامل کیا جائے گا۔

چونکہ ہر ماڈیول خود کو بیان کرنا جانتا ہے، بگ رپورٹیں ناکامی کے وقت پورے اسٹیک، کنکشن ٹیبلز، چینل اسٹیٹس، اور کنٹرولر کی فعالیت کے مسلسل اسنیپ شاٹس کو حاصل کرتی ہیں، بغیر انسانوں کو کسی بھی چیز کی ضرورت کے۔

یہ فرق ہے "براہ کرم جب میں اسے دیکھ رہا ہوں دوبارہ پیش کریں” اور "مجھے ایک بگ رپورٹ بھیجیں جو آپ کے پاس پہلے سے موجود ہے۔” وقت پر منحصر اجزاء جیسے بلوٹوتھ کے لیے، یہ اختلافات اکثر ایک مکمل راز ہوتے ہیں۔

روٹ کینال کے ساتھ سرٹیفیکیشن کی جانچ اور جانچ

اس تمام ماڈیولریٹی کا انعام ٹیسٹنگ انفراسٹرکچر ہے، جس کے دو قابل ذکر حصے ہیں: پہلا سرٹیفیکیشن ٹیسٹوں کا ایک مجموعہ ہے، جسے عام طور پر سرٹ ٹیسٹ کہا جاتا ہے، جو Python میں لکھا جاتا ہے اور ایک gRPC اگواڑے کے ذریعے ڈرائیونگ ماڈیول ہوتا ہے۔ دوسرا روٹ کینال ہے، جو ایک ورچوئل بلوٹوتھ کنٹرولر ہے۔

روٹ کینال خصوصی توجہ کا مستحق ہے۔ یہ ایک بلوٹوتھ کنٹرولر کا ایک سافٹ ویئر نفاذ ہے جو HCI سے مراد ہے۔ لیکن وائرلیس کے بجائے، ایک مصنوعی جسمانی پرت ہے.

ایک سے زیادہ اسٹیک ایک روٹ کینال مثال سے جڑ سکتے ہیں، اسٹیک کو ایک دوسرے کے وائرلیس رینج میں ہونے کے طور پر ماڈلنگ کرتے ہوئے، اشتہارات اور کنکشن کی درخواستوں کو ایک اسٹیک سے دوسرے اسٹیک میں بھیج سکتے ہیں۔ اس کا مطلب یہ ہے کہ دو آلات کے درمیان جوڑی بنانے اور ڈیٹا کی منتقلی کا پورا منظرنامہ لینکس سسٹم پر بغیر کسی بلوٹوتھ ہارڈ ویئر کے مسلسل انضمام کے ساتھ چلایا جا سکتا ہے۔ تعمیرات اس مظاہر کو ختم کرتی ہے جس نے روایتی ٹیسٹ کہانیوں کو مایوس کن بنا دیا تھا، جیسے کہ حقیقی دنیا میں ریڈیو کی خرابیاں، ریڈیو کی مداخلت، وقت کا گھماؤ، اور ایک ساتھی کا مائکروویو کے ساتھ گزرنا۔

Cert ٹیسٹ اسے آپس میں جوڑتا ہے۔ یعنی، ٹیسٹ کے تحت اسٹیک شروع کریں اور دوسرا حوالہ اسٹیک، دونوں کو روٹ کینال سے جوڑیں، اور ان کے درمیان کراسنگ پیکٹ پر زور دیں۔

class HciTest(GdBaseTestClass):

    def test_local_hci_cmd_and_event(self):
        # Send an HCI command through the DUT's facade.
        self.dut.hci.SendCommand(
            hci_facade.Command(payload=bytes(ResetBuilder().Serialize())))

        # Assert the expected completion event comes back.
        assertThat(self.dut.hci.get_event_stream()).emits(
            HciMatchers.CommandComplete(OpCode.RESET))

یہ ٹیسٹ HCI پرت کو بغیر کسی ہارڈ ویئر کے شروع سے ختم تک چلاتا ہے۔ self.dut ٹیسٹ کے تحت ڈیوائس ایک حقیقی GD اسٹیک مثال ہے جو ایک gRPC اگواڑے کے پیچھے چل رہی ہے۔ SendCommand سیریلائزیشن ResetBuilderیہ Python بائنڈنگز کے ذریعے بنایا گیا ہے جو اسی PDL سے C++ کوڈ سے تیار کیا گیا ہے اور اسے اسٹیک پر HCI اگواڑے کی طرف دھکیل دیا گیا ہے۔ اس کے بعد ٹیسٹ اس بات پر زور دیتا ہے کہ یہ ایک میچر کا استعمال کرتے ہوئے ری سیٹ اوپکوڈ کے لیے ایک انسٹرکشن مکمل ہونے کا ایونٹ تیار کرتا ہے جو اسٹیک سے ایونٹ کا سلسلہ لیتا ہے، اسے ایک بار چیک کرتا ہے، اور ریس چھوڑنے کے بجائے ایونٹ کے آنے کا انتظار کرتا ہے۔

پورا منظر نامہ CI میں متعین طور پر چلتا ہے۔ اگر یہ ٹیسٹ پاس ہو جاتا ہے، تو آپ جانتے ہیں کہ HCI کمانڈ پاتھ کام کر رہا ہے۔ اگر آپ ناکام ہوتے ہیں، تو آپ ہر بار اسی طرح ناکام ہوتے ہیں۔ یہ واحد سب سے قیمتی جائیداد ہے جو کسی ٹیسٹ کے پاس ہو سکتی ہے۔

ڈینٹل فلوس: گیبلڈورسے اینڈرائیڈ سے آگے

GD اسٹیک انتہائی پورٹیبل نکلا کیونکہ یہ اینڈرائیڈ کے اندر موجود ہونے کی بجائے اپنے OS تجریدات پر بنایا گیا ہے۔ Floss، Fluoride Linux OS Stack کے لیے مختصر، ایک ایسا پروجیکٹ ہے جو Gabeldorsche کور کو ڈیسک ٹاپ لینکس اور ChromeOS کے لیے بلوٹوتھ اسٹیک کے طور پر چلاتا ہے۔ ہم وہی ماڈیولز، وہی پیکٹ لائبریریاں، اور وہی ٹیسٹ انفراسٹرکچر دوبارہ استعمال کرتے ہیں، اور اسے Android AIDL کے بجائے D-Bus انٹرفیس کے ذریعے لینکس کی دنیا کے سامنے لاتے ہیں۔

یہ فن تعمیر کا ایک حقیقی ثبوت ہے۔ وہی کنکشن مینجمنٹ، HCI فلو کنٹرول، راؤنڈ رابن شیڈولنگ، اور PDL جنریشن پارسر جو آپ کے فون کے ایئربڈز کو منسلک رکھتا ہے آپ کے لیپ ٹاپ پر بھی چلتا ہے۔ کیونکہ اس منطق میں سے کوئی بھی پہلی جگہ اینڈرائیڈ میں شامل نہیں کیا گیا تھا۔

ہر پلیٹ فارم کے مختلف حصوں کو کنارے پر دھکیل دیا جاتا ہے، چپ کے ساتھ بات چیت کرنے والی ٹرانسمیشن کو نیچے رکھا جاتا ہے، OS کے ساتھ بات چیت کرنے والا انٹرفیس سب سے اوپر رکھا جاتا ہے، اور بڑے پروٹوکول کے وسط میں اشتراک کیا جاتا ہے۔

ایک ایسا فن تعمیر جو بالکل مختلف آپریٹنگ سسٹم کو صاف ستھرا طور پر پورٹ کرتا ہے وہ ہے جس میں واقعی صاف ستھرا سیون ہوتا ہے، نہ صرف یہ کہ اس نے ایسا کیا۔

خلاصہ

Gabeldorsche اس وقت آتا ہے جب ٹیم نے فیصلہ کیا کہ ایک دہائی کے ناقابل اعتماد بلوٹوتھ مسائل کو حل کرنے کا طریقہ علامات کے بجائے فن تعمیر کو حل کرنا تھا۔

پورا ڈیزائن چند مستقل خیالات پر مبنی ہے جو تمام تہوں میں دہرائے جاتے ہیں۔ ایک ٹاسک واضح، اعلان کردہ انحصار کے ساتھ ماڈیولز پر مشتمل ہوتا ہے کہ رجسٹری صحیح ترتیب سے شروع ہوتی ہے اور الٹ ترتیب میں ختم ہوتی ہے۔ کنکرنسی اس تھریڈ کو پیغامات کی منتقلی ہے جو ڈیٹا کا مالک ہے، جو ری ایکٹرز اور ہینڈلرز پر بنایا گیا ہے۔ لہذا، اسٹیک شاذ و نادر ہی تالے کا استعمال کرتا ہے، اور جو تالے استعمال کرتا ہے وہ نایاب اور جان بوجھ کر ہوتے ہیں۔

ڈیٹا تہوں کے درمیان رد عمل والی قطاروں کے ذریعے منتقل ہوتا ہے جو مفت بیک پریشر فراہم کرتی ہے، ایک راؤنڈ رابن شیڈیولر کنٹرولر بفرز کو کنکشنز میں کافی حد تک شیئر کرتا ہے تاکہ آڈیو اور بلک ٹرانسفر ایک ساتھ رہیں، اور L2CAP چھوٹے، واحد مقصدی مراحل کی پائپ لائن ہے نہ کہ ایک سنگل۔ پیکٹس کو آفیشل تصریح سے تیار کردہ کوڈ کے ساتھ پارس کیا جاتا ہے، جس سے میموری سیفٹی بگز کی ایک پوری میزبانی کو ختم کیا جاتا ہے اور C++ اور Python کے راستوں کو ایک جیسا رکھا جاتا ہے، بائٹ فار بائٹ۔ کنکشنز اور چینلز انٹیجر ہینڈل کے بجائے ایک عالمی ٹیبل کے ساتھ ایک یقینی زندگی بھر کے ساتھ اشیاء کے مالک ہیں، اور سیکورٹی ایک رن ٹائم چیک کے بجائے ٹائپ اور رجسٹریشن سے منسلک ایک خاصیت ہے جسے کوئی بھول سکتا ہے۔

آزمائشی کہانی وہی ہے جو باقیوں کو قابل اعتماد بناتی ہے۔ ہر پرت ایک gRPC اگواڑے کو بے نقاب کرتی ہے، بلڈ سسٹم ہر تبدیلی پر پیکٹ کوڈ کو دوبارہ تخلیق کرتا ہے، ڈمپسیس ہر بگ رپورٹ میں ایک مکمل اسٹیٹ اسنیپ شاٹ کیپچر کرتا ہے، اور RootCanal ایک ورچوئل کنٹرولر فراہم کرتا ہے لہذا پورا ملٹی ڈیوائس منظر نامہ ریڈیو کے بغیر مسلسل انضمام کے ساتھ متعین طور پر چلتا ہے۔ ایک ٹیسٹ جو ہر بار اسی طرح ناکام ہوتا ہے اس اسٹیک سے زیادہ قیمتی ہے جو صرف کبھی کبھار کام کرتا ہے، اور اس اصول نے پوری تحریر کو آگے بڑھایا۔

اگر آپ قریب سے دیکھنا چاہتے ہیں تو، کوڈ بلوٹوتھ مین لائن ماڈیول کے تحت AOSP میں ہے، اور وہی کور اب لینکس ڈیسک ٹاپ پر فلوس کے ذریعے چلتا ہے۔ پڑھیں .pdl پھر، تیار کردہ ہیڈرز کو پڑھ کر اور پھر L2CAP سے ایک ACL پیکٹ کو راؤنڈ رابن شیڈیولر کے ذریعے HCI پرت تک لے جانے سے، پورے پروجیکٹ کا فلسفہ ایک دوپہر میں واضح ہو جاتا ہے۔ یہ ایک اسٹیک ہے جسے سمجھنے کے لیے ڈیزائن کیا گیا ہے، اور بلوٹوتھ کے لیے یہ اب بھی ایک چھوٹے معجزے کی طرح محسوس ہوتا ہے۔

Scroll to Top