الكتابات/blog/2026/07
Blog31 يوليو 2026·6 دقيقة

رموز QR المتحركة: قناة حقيقية لنقل الملفات

أكواد النافورة حوّلت رموز QR المتحركة إلى قناة نقل ملفات بسرعة 129 كيلوبايت/ث دون شبكة أو بلوتوث أو اقتران. إليك كيف يعمل هذا الرابط الضوئي فعليًا وأين يفيد.

انتشر هذا الأسبوع عرض توضيحي يبدو كخدعة سحرية. هاتف يعرض تدفقًا لا ينتهي من رموز QR الوامضة. هاتف ثانٍ يوجّه كاميرته نحو شاشة الأول. وبعد عشرين ثانية، يكون الملف قد وصل.

لا واي فاي. لا بلوتوث. لا اقتران، ولا شبكة مشتركة، ولا تطبيق يُثبَّت. الشيء الوحيد الذي يعبر المسافة بين الجهازين هو الضوء.

المشروع — Decimen Optical Transfer، برخصة MIT، بُني في ليلة واحدة باستخدام Claude Code — سجّل نحو 129 كيلوبايت في الثانية من الإنتاجية الفعلية، مقابل سقف نظري قريب من 186 كيلوبايت/ث. أي أسرع بنحو ثلاثين ضعفًا من التطبيقات التسلسلية لعام 2024 التي كانت تدور حول 4 كيلوبايت/ث. هذه ليست حيلة طريفة، بل قناة بيانات حقيقية، وما يجعلها ممكنة هو عائلة من أكواد تصحيح الفقد لم تسنح لأغلب مطوّري الويب فرصة للتعامل معها.

المشكلة التي تخلقها القناة الضوئية

كل بروتوكول شبكي كتبته يفترض وجود قناة عكسية. بروتوكول TCP يعيد الإرسال لأن المستقبِل يستطيع القول: «الإطار رقم 47 لم يصل». وطلبات HTTP Range موجودة لأن العميل يستطيع طلب موضع بايت محدد.

أما شاشة تعرض رمز QR على كاميرا فلا تملك أيًّا من ذلك. إنها بث محض. المرسِل لا يعرف إن كان المستقبِل ينظر أصلًا، ولا إن كانت الإضاءة سيئة، ولا إن أسقطت الكاميرا إطارًا أثناء ضبط التركيز، ولا إن بدأ المستقبِل المشاهدة من منتصف التدفق.

التصميم الساذج هو تقسيم الملف إلى قطع، وترقيمها، ثم تكرار التسلسل في حلقة. هذا ما يبنيه الجميع تقريبًا أول مرة، وهو يفشل فشلًا ذريعًا. كاميرات الهواتف تفقد الإطارات باستمرار — وفقدان إطار عند 15 إطارًا في الثانية يعني انتظار دورة كاملة حتى تعود تلك القطعة. افقد بضع قطع في كل دورة، ولن يكتمل النقل أبدًا. مشروع txqr قاس هذا منذ 2018: عند حجم قطعة يقارب 1000 بايت، كان فقدان الإطار «شبه مضمون» ويظل فاكّ الترميز عالقًا في انتظار لا ينتهي.

ولا يمكن إصلاح ذلك بإعادة المحاولة، ببساطة لأنه لا توجد قناة تُعاد المحاولة عبرها.

أكواد النافورة: تخلَّ عن أرقام التسلسل

أكواد النافورة تحلّ هذه المشكلة تحديدًا. اسمها العلمي أكواد الفقد اللامعدّلة، والحدس كامن في التسمية: النافورة ترشّ عددًا لا محدودًا من القطرات، ولا يهم أيّ قطرة تلتقطها — تكفي أن تمدّ دلوًا حتى يمتلئ.

فبدلًا من إرسال القطعة الأولى ثم الثانية ثم الثالثة، يعمل مُرمِّز Luby Transform هكذا:

  1. تقسيم الملف إلى K كتلة مصدرية.
  2. لكل إطار، اختيار درجة d عشوائية من توزيع مضبوط بعناية (توزيع السوليتون المتين).
  3. اختيار d كتلة مصدرية عشوائيًا ودمجها بعملية XOR.
  4. إرسال الناتج مع ترويسة تكفي لاستنتاج أي الكتل دخلت فيه.

كل إطار خليط مختلف من الملف بأكمله. لا يوجد إطار هو «القطعة الثالثة». يجمع المستقبِل الإطارات بأي ترتيب، وحالما يجمع نحو K × 1.15 إطارًا مميزًا، يستطيع حلّ المنظومة واستعادة الأصل — كلفة إضافية قدرها 15 بالمئة مقابل لامبالاة تامة بهوية الإطارات الواصلة.

وفكّ الترميز بسيط ببراعة. أي إطار درجته 1 هو كتلة مصدرية مباشرة. تُزال تلك الكتلة بعملية XOR من كل إطار آخر تضمّنها، فتنخفض درجته بمقدار واحد، وقد يولّد ذلك إطارات جديدة من الدرجة 1، وهكذا تتوالى. إنها تفاعل متسلسل.

// مُرمِّز LT — إطار واحد، مشتق بالكامل من رقم تسلسله
function encodeFrame(blocks, seq, sessionId) {
  const rng = seededRng(sessionId ^ seq);        // المستقبِل يعيد إنتاج هذا بالضبط
  const degree = robustSoliton(rng, blocks.length);
  const picked = sampleDistinct(rng, blocks.length, degree);
 
  const payload = new Uint8Array(blocks[0].length);
  for (const i of picked) {
    for (let b = 0; b < payload.length; b++) payload[b] ^= blocks[i][b];
  }
  return concat(header(sessionId, seq, blocks.length, payload.length), payload);
}

لاحظ ما لا تحتويه الترويسة: قائمة الكتل المصدرية. فهي تحمل رقم التسلسل فقط، ويعيد المستقبِل اشتقاق المجموعة العشوائية نفسها من البذرة نفسها. بهذا تبقى الترويسة عند نحو 20 بايت بدل قائمة فهارس متغيرة الطول — وعلى قناة تُكلّف فيها كل بايت وحدات إضافية في رمز QR، فإن ذلك يُحدث فرقًا.

كما يخلق ذلك فخًّا دقيقًا وقع فيه مؤلف Decimen: الدالة Math.log تُرجع قيمًا مختلفة اختلافًا طفيفًا بين محرك V8 ومحرك JavaScriptCore. فإن اعتمد توزيع الدرجات لديك على حساب الفاصلة العائمة، اشتقّ مستقبِل آيفون ومرسِل أندرويد مجموعتَي كتل مختلفتين، وتحوّل كل إطار إلى بيانات فاسدة. الحساب الحتمي عبر المحركات ليس خيارًا هنا، بل ضرورة.

من أين تأتي السرعة فعليًا

الإنتاجية على رابط ضوئي حاصل ضرب أربعة عوامل، والحدس الساذج يحسّن العامل الخطأ.

العاملالحركة الساذجةما ينجح فعلًا
معدل الإطاراتالدفع إلى 60 إطارًا/ثالمواءمة مع معدل التقاط الكاميرا الحقيقي، لا الشاشة
كثافة رمز QRالتشبّع عند الإصدار 40الإصدار 25 إلى 30 — كثيف بما يكفي للحمولة، واضح بما يكفي للقراءة رغم التشويش
تصحيح الأخطاءالمستوى H «احتياطًا»المستوى L (7 بالمئة) — طبقة النافورة تعالج الفقد أصلًا
حجم القطعة1500 بايت طلبًا للكفاءةمن 550 إلى 900 بايت؛ وما فوق ذلك يجعل الكثافة غير مقروءة

الصف الثالث هو الأكثر مخالفة للحدس. رموز QR تحمل تصحيح Reed-Solomon الخاص بها، ورفعه إلى المستوى H يستهلك 30 بالمئة من كل إطار في التكرار. لكنك تملك أصلًا طبقة متسامحة مع الفقد فوقه. الإطار الذي يتعذّر فكّه ليس سوى إطار لم توصله النافورة، والنافورة لا تكترث. تعزيز التكرار على مستوى QR هو دفع الثمن مرتين.

معدل الإطارات هو ساحة خصوصيات المنصات. على نظام iOS، طلب { ideal: 60 } من getUserMedia يمنحك 30 إطارًا في الثانية بصمت؛ وتحتاج إلى { exact: 60 } بعرض 1280 بكسل للحصول على 60 فعليًا. كما أن requestVideoFrameCallback هي البدائية الصحيحة للالتقاط — أما حلقة setInterval فستأخذ عيّنة من الإطار نفسه مرتين وتُفوّت إطارات أخرى تمامًا.

// المستقبِل — فكّ ترميز كل إطار كاميرا حقيقي، لا كل نبضة مؤقّت
function pump(video, canvas, ctx, onSymbol) {
  video.requestVideoFrameCallback(() => {
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    const result = zxing.readBarcode(
      ctx.getImageData(0, 0, canvas.width, canvas.height),
      { formats: ["QRCode"], tryHarder: false }   // tryHarder يكلّف أكثر مما ينقذ
    );
    if (result?.bytes) onSymbol(result.bytes);
    pump(video, canvas, ctx, onSymbol);           // إعادة التسليح
  });
}

ملاحظتان عمليتان على هذا المقتطف. الخيار tryHarder يبدو مجانيًا وهو ليس كذلك: على تدفق يصل فيه الإطار الصالح التالي خلال 16 مللي ثانية، فإن إنفاق 40 مللي ثانية لإنقاذ إطار حدّي خسارة صافية. كما يجب إعادة تسليح كل requestVideoFrameCallback مرة واحدة بالضبط؛ فإن فُكّك تدفق الكاميرا وأُعيد بناؤه دون إلغاء السلسلة، انتهى بك الأمر إلى استدعاءات شبحية تفكّ الترميز داخل لوحة ميتة.

ويضيف متصفح Safari عقبة أخرى: واجهة BarcodeDetector ما تزال غير منفَّذة في WebKit، ما يجعل بناء zxing-cpp بلغة WebAssembly المسار الوحيد القابل للنقل. وإن كنت قد تابعت خروج WebAssembly من المتصفح، فهذه هي الصورة المعكوسة: Wasm يسدّ ثغرة داخله هذه المرة.

المشهد الحالي

أُعيد اختراع هذه الفكرة مرارًا، وهو عادةً دليل على جودتها.

المشروعالمرمِّزالإنتاجية المعلنةملاحظات
Decimen Optical TransferLT، سوليتون متيننحو 129 كيلوبايت/ثيعمل في المتصفح، MIT، يوليو 2026
RaptorQRRaptorQ (RFC 6330) عبر Wasm180 إلى 254 كيلوبايت/ث (مختبريًا)تشغيل QR متوازٍ، تطبيق PWA، مرسِل عبر سطر الأوامر
txqrأكواد نافورة، بلغة Goنحو 9 كيلوبايت/ث كذروةالأصل من 2018؛ عبر Gomobile وGopherJS
libcimbarنافورة مع ضغط zstdمرتفعة، ملفات حتى 33 ميغابايتيتخلى عن QR لصالح صيغة أكثف

أرقام RaptorQR مأخوذة من تشغيل أربعة رموز QR جنبًا إلى جنب عند 30 إطارًا/ث على آيفون 16 بإضاءة مضبوطة: أرقام حقيقية، لكنها سقف لا وعد. كما أن RaptorQ هو المرمِّز الأكثر جدية — كود نظامي معياري بخصائص كلفة إضافية أفضل بكثير من LT البسيط، مقابل الحاجة إلى تنفيذ حقيقي بدل خمسين سطرًا من XOR.

أما libcimbar فيخطو الخطوة الصادقة ويتخلى عن QR كليًا. فحالما تُقرّ بأنك تبني مودمًا ضوئيًا، تصبح مواصفة QR — المصمَّمة لتُقرأ عن ملصق مطبوع بزاوية مائلة — وزنًا زائدًا. الشبكات الكثيفة الملوّنة تحمل أضعاف ذلك في الإطار الواحد.

أين يفيد هذا فعلًا

من السهل استبعاد الفكرة بحجة وجود AirDrop. الاستخدامات حقيقية لكنها محدّدة.

البيئات المعزولة. توقيع معاملة على جهاز لم يتصل بشبكة قط، ثم إخراج الكتلة الموقّعة عبر الكاميرا، نمط راسخ في محافظ العملات الصلبة. رموز QR المتحركة بأكواد النافورة ترفع سقف الحمولة من رمز ثابت واحد إلى ملفات كاملة.

فجوات الاتصال والانقطاعات. في أنحاء واسعة من منطقة الشرق الأوسط وشمال أفريقيا، تكون بيانات الهاتف مُحتسَبة أو مكلفة أو متقطعة. بروتوكول لا يحتاج بنية تحتية إطلاقًا — ولا حتى نقطة اتصال محلية — يتدهور بأناقة حيث تنهار المزامنة السحابية. وهذا هو الحدس نفسه وراء معمارية local-first: اعتبار الشبكة رفاهية لا شرطًا.

النقل تحت قيود السياسات. كثير من بيئات المؤسسات والجهات الحكومية تمنع أجهزة USB وتحجب خدمات مشاركة الملفات، لكن لم يكتب أحد سياسة تمنع توجيه هاتف نحو شاشة. وكون ذلك ميزة أم ثغرة يتوقف كليًا على الجهة التي تقف فيها.

وتستحق النقطة الأخيرة التحفظ الذي يذكره مؤلف Decimen بنفسه: العزل المقصود هنا ضوئي لا أمني. الكاميرا الموجَّهة نحو شاشة هي مسار بيانات. وكل من يرى الشاشة — بما في ذلك كاميرا مراقبة أو نافذة أو انعكاس — يستقبل البثّ ذاته. لا توجد خطوة اقتران ولا مصادقة. فإن كانت الحمولة مهمة، فشفّرها قبل أن تتحول إلى إطارات؛ فطبقة النقل لا تمنحك شيئًا. وينطبق هنا الانضباط ذاته المتّبع مع أي قناة غير موثوقة، ولمن يفكّر في الصورة الأوسع تغطي ملاحظاتنا حول السيادة الرقمية الجانب المتعلق بالسياسات.

كيف تجرّبه بنفسك

الحزمة كاملة من ثلاثة أجزاء: مُرمِّز LT أو RaptorQ، ومحرّك عرض QR سريع، وقارئ باركود بلغة Wasm. مشروع RaptorQR هو نقطة البداية الأكمل — فهو يوفّر مرسِلًا في المتصفح، ومستقبِلًا بصيغة PWA، وواجهة سطر أوامر. أما Decimen فهو الأسهل قراءةً إن أردت فهم المرمِّز لا مجرد استخدامه.

ابدأ من المعاملات المعروف نجاحها — رمز QR بالإصدار 25، وتصحيح بالمستوى L، وقطع بحجم 700 بايت، والتقاط تقوده requestVideoFrameCallback — ثم اضبط منها. وقِس الرموز المفكوكة في الثانية، لا الإطارات المعروضة في الثانية. الفجوة بين هذين الرقمين هي مشكلتك الهندسية بأكملها.

الخلاصة

الأمر المثير للاهتمام هنا ليس السرعة، بل أن قيدًا يعدّه الجميع قاتلًا — لا قناة عكسية، ووسط فاقد، ولا مصافحة — تبيّن أن له إجابة نظيفة عمرها أربعون عامًا، جاهزة في نظرية الترميز. صُمّمت أكواد النافورة لهذا الشكل من المشكلات تحديدًا، وتظل قليلة الاستعمال لأن معظم المطورين لا يصادفون قناة بلا إعادة إرسال.

الشاشات والكاميرات هما أكثر جهازَي إدخال وإخراج انتشارًا على الكوكب، وكل هاتف يملك كليهما. معاملة هذا الثنائي كطبقة نقل فكرة جيدة فعلًا — وقد لزم كود لامعدّل لجعلها عملية.


المصادر: تحليل Decimen Optical Transfer · RaptorQR · txqr · نقل البيانات عبر QR المتحرك