
وحدة فك تشفير/ترميز لإطارات بروتوكول AirMAX اللاسلكي من Ubiquiti؛ تحليل التقاطات pcap أو الالتقاط المباشر، اكتشاف الأجهزة، فحص البرامج الثابتة المعرضة للثغرات، صياغة حزم مشوهة، ومحاكاة أهداف AirMAX AC/M.
وحدة فك ترميز وترميز لإطارات بروتوكول Ubiquiti AirMAX، تغطي كلا سلسلتي AC (البرنامج الثابت WA) وM (البرنامج الثابت XW/XM).
ksy/. أما حمولة AC
(المقيّدة بالإصدار، والتي تتبدّل حسب msg_type، مع إزالة قناع XOR لـ deauth)
فهي مكتوبة يدويًا في ac.py — وهذا المنطق لا يناسب Kaitai المخصص للتحليل فقط.| النسخة | فك الترميز | الترميز | تكرار pcap |
|---|---|---|---|
| AC | ✅ جميع أنواع الرسائل الخمسة (beacon، assoc req/resp، probe req، deauth) | ✅ معكوس مطابق للبايت + دوال البناء (builders) + seal | ✅ |
| M | جزئي (9 بايتات موثقة؛ والباقي كـ unknown_rest) | ✅ يعيد الرأس الموثّق + unknown_rest بلا فقدان | ✅ |
| Routerboard.com IE (مرافق لـ M) | ✅ اسم الجهاز + قائمة sub-IE | غير متاح | ✅ |
تنسيق إطار AC على الشبكة يتبع
docs/ac_wire_format.md
(مؤكَّدة مقابل ubnt_poll_host.ko وفقًا لـ [P]). جميع الأعداد الصحيحة متعددة البايت في AC
big-endian.
pyrmax.ac.encode(AcPacket) -> bytes — مكتوب يدويًا ومطابق للبايت
معكوس decode() (إلى جانب seal() وto_ie() والمنشئات build_*).pyrmax.m.encode(MPacket) -> bytes — نفس الشيء لـ M (+ build_m،
seal, to_ie).[open] نهائيًا — خريطة البتات الدقيقة لـ cap_flags،
mixed_mode، / (assoc_req)، /
(assoc_resp)، و الخاص بـ deauth (قابل للاستخراج لكن مفتاحه لم
يُهندَس عكسيًا بعد، لذا لا يمكن التحقق منه). انظر
§11-§12.توفر الحزمة واجهة CLI تعتمد على أوامر فرعية. شغّلها كالتالي:
python -m pyrmax COMMAND ....```
usage: python -m pyrmax [-h] [--version] COMMAND ...
COMMAND parse Print each AirMAX frame in detail. discover Summarize devices observed in the capture. scan Live-scan for AirMAX devices and flag vulnerable firmware. emulate Emulate AirMAX AC/M devices (fake targets for scanners).
تأخذ `parse` و`discover` **إما** ملف pcap/pcapng (موضعي)
**أو** واجهة لاسلكية حية عبر `-i / --iface IFACE`. الاثنان
متنافيان. يقبل `scan` نفس خيارات المصدر (بالإضافة إلى
وضع `--active` الذي ينطبق فقط على الواجهة الحية)؛ `emulate`
خاص بالواجهة الحية فقط.```sh
python -m pyrmax parse capture.pcap # offline
python -m pyrmax discover capture.pcap
sudo python -m pyrmax parse -i wlan0mon # live (needs root)
sudo python -m pyrmax discover -i wlan0mon
الالتقاط المباشر يفترض أن الواجهة في وضع المراقبة بالفعل على
القناة محل الاهتمام — لا يهيئ pyrmax أيًا منهما. يتطلب الأمر
الامتداد الاختياري [live] (pip install pyrmax[live]) الذي يجلب
pcapy-ng. اضغط Ctrl-C للتوقف: يُبلّغ parse عن عدد الإطارات التي
عالجها؛ ويطبع discover ملخص الأجهزة المجمّع عند الخروج.
رموز الخروج (مشتركة بين الأمرين وكلا نمطي المصدر): 0 عند
النجاح (بما في ذلك "لا توجد إطارات AirMAX" — وهي نتيجة صحيحة)، 1 لأخطاء
تنسيق الالتقاط (نوع رابط خاطئ، ملف تالف، تعذّر فتح
الواجهة)، 2 لملف مفقود أو وسائط مصدر غير صالحة.
parse — تفريغ لكل إطار```shpython -m pyrmax parse capture.pcap
كل إطار إدارة 802.11 يحمل IE خاص ببائع AirMAX ينتج
كتلة واحدة: حزمة AC، وحزمة M، وأي IE مصاحب من Routerboard.com
موجود في نفس الإطار.```
Found 1 AirMAX frame(s) in capture.pcap: 0 AC, 1 M (1 with Routerboard companion).
=== Frame #0 [M] ts=1765494974.955358 ===
802.11 src=04:18:d6:0e:0c:42 dst=24:a4:3c:88:d8:22 bssid=04:18:d6:0e:0c:42
AirMAX M
version 15
msg_type BEACON (raw=1)
src_mac 04:18:d6:0e:0c:42
enable 1
unknown_rest b700000000000000000000040418d60e0c420000000000 (23B)
Routerboard.com IE
oui_type 0
unknown 0000
device_name 'AP Sur HY1315'
sub_ie subtype=1 (30B) 040000001f660902ff0f4150205375722048593133313500000000000000
نفس أداة الاجتياز أحادية المرور متاحة برمجيًا باسم
pyrmax.pcap.iter_airmax(path) — تُنتج سجلًا واحدًا من نوع
AirmaxRecord(meta, ac, m, routerboard) لكل إطار يحمل AirMAX، وبالتالي
لن تحتاج إلى مطابقة عناصر معلومات M وRouterboard يدويًا.
discover — ملخّص الجهاز```shpython -m pyrmax discover capture.pcap
تُجمَّع الإطارات حسب عنوان MAC المصدر لمعيار 802.11، وتتراكم الأقران، وتندمج ملاحظات حمولة AC / حمولة M / اسم جهاز Routerboard في كتلة واحدة لكل جهاز.```
2 device(s) observed in capture.pcap across 12 AirMAX frame(s).
24:5a:4c:44:57:fd (AC)
radioname 'LB1'
ssid 'labalUBI2'
ac_msg_types BEACON
ac_version 9
cap_flags 0x0000003e
mixed_mode 0
frames 8
first seen 1767046123.708745
last seen 1767046129.012448
peers (broadcast only)
04:18:d6:0e:0c:42 (M)
device_name 'AP Sur HY1315'
msg_types BEACON
m_version 15
m_enable 1
frames 4
first seen 1765494974.955358
last seen 1765494980.341110
peers 24:a4:3c:88:d8:22
نفس التجميع هو أيضًا دالة عامة:```python from pyrmax.devices import summarize from pyrmax.pcap import iter_airmax
devices = summarize(iter_airmax("capture.pcap")) for mac, dev in devices.items(): print(mac.hex(":"), dev.device_name or dev.radioname, dev.peers)
## الاستخدام
### فك ترميز رسالة AirMAX AC واحدة
يتوقع المُفكِّك وحدات البايت الخاصة بـ Vendor Specific IE في 802.11 **بدءًا من
OUI** — يجب أن يكون غلاف IE (معرّف العنصر `0xDD` + الطول) قد أُزيل
بالفعل. يتم استخراج `src_mac` / `dst_mac` من SA / DA لإطار 802.11
الخارجي.```python
from pyrmax import ac
packet = ac.decode(
data, # bytes starting at b"\x00\x27\x22"
src_mac="aa:bb:cc:dd:ee:ff", # accepts str ("aa:bb:..." or "aa-bb-..."
# or "aabbcc...") and raw 6-byte bytes
dst_mac="ff:ff:ff:ff:ff:ff", # optional — defaults to broadcast
)
packet.msg_type # <MsgType.BEACON: 1>
packet.version # 9 (the wire-format epoch / version gate)
packet.src_mac # b'\xaa\xbb\xcc\xdd\xee\xff' (integrity-checked)
packet.radioname # "lab-rx-1" (convenience prop, delegates to body)
packet.ssid # "NetA"
packet.cap_flags # 0x3e (None for msg types that have no cap_flags)
# The per-message-type fields live on packet.body, one of:
# BeaconBody | AssocReqBody | AssocRespBody | ProbeReqBody | DeauthBody
body = packet.body
if isinstance(body, ac.BeaconBody):
body.mac_0c # the radio's own MAC / BSSID
body.cap_flags # u32 capability bitfield (§11)
body.mixed_mode # u32 [open]
msg_type هو أحد القيم BEACON / ASSOC_REQ / ASSOC_RESP / PROBE_REQ /
DEAUTH. حقول الذيل المرتبطة بالإصدار (field_9c, rssi, fwname, txpower,
…) تكون قيمتها None عندما يكون version الخاص بالإطار أقل من عتبتها. يقوم إطار
Deauth بإلغاء XOR على src_mac باستخدام nonce الخاص بـ jiffies قبل فحص السلامة
ويُظهر enc_token ذو الـ 16 بايت كبايتات معتمة (مفتاحه لم يُعكس بعد،
لذا لا يمكن التحقق منه).
الهيئات التي تحمل TLVs للأسماء (beacon, assoc_req) تحافظ على التدفق —
بما في ذلك إدخال Padding الختامي — حرفيًا على body.tlvs. يتم إبراز radioname,
ssid و fwname كخصائص تسهيلية على الحزمة؛
كل شيء آخر يبقى خامًا.```python
body = packet.body
for tlv in getattr(body, "tlvs", ()):
if tlv.tag == ac.TlvTag.PADDING:
continue
print(f"{tlv.tag.name:<10} ({len(tlv.data)} bytes): {tlv.data!r}")
### فك ترميز رسالة AirMAX M واحدة
نفس الشكل، سطح أصغر — فقط 9 بايتات من حمولة M موثقة؛ أما الباقي فيُحفظ حرفيًا في `unknown_rest`. **ملاحظة:** حمولة M نفسها لا تحتوي على حقل SSID — لذلك، انظر إلى عنصر معلومات SSID القياسي في 802.11 في المنارة أو استجابة الفحص المحيطة (يظهر كـ `FrameMeta.ssid` عند التكرار عبر `pyrmax.pcap`).```python
from pyrmax import m
packet = m.decode(data, src_mac="aa:bb:cc:dd:ee:ff")
packet.version # 1
packet.msg_type # <MsgType.BEACON: 1>
packet.src_mac # b'\xaa\xbb\xcc\xdd\xee\xff'
packet.enable # 1
packet.unknown_rest # b'\xde\xad\xbe\xef...' # opaque, RE pending
encode(packet, dst_mac=…) هو المعكوس الحرفي البايتي لـ decode —
encode(decode(x)) == x لإطار مكتمل البنية. يقوم بتسلسل الجسم
(مع إعادة تطبيق بوابات الإصدار، وبالنسبة لـ deauth، قناع src_mac XOR)، ويحشو إلى
كتلة AES، ويشفر باستخدام المفتاح المشتق من packet.src_mac، ثم يُخرج
البايتات من OUI فصاعدًا. لفه باستخدام to_ie() للحصول على IE بائع كامل بصيغة 0xDD.
بُنيات build_* توفر عليك تجميع الأجسام المتداخلة يدويًا:```python
from pyrmax import ac
pkt = ac.build_beacon(src_mac="24:5a:4c:44:57:fd", radioname="LB1", ssid="labalUBI2", cap_flags=0x3e) ie = ac.to_ie(ac.encode(pkt)) # full 802.11 vendor IE, ready to embed
deauth = ac.build_deauth(src_mac="24:5a:4c:44:57:fd", jiffies_nonce=0xdeadbeef) raw = ac.encode(deauth, dst_mac="24:a4:3c:88:d8:22")
بالنسبة للفيزينغ / إثباتات المفهوم، تقوم `seal()` بتشفير نص عادي **اعتباطي** وتبني الترويسة الخارجية — لذا يمكنك صياغة حمولات مشوّهة عمدًا (`msg_type` زائف، أطوال خاطئة، أجسام فرعية) لا يمكن للمشفّر المنظّم أن ينتجها أبدًا:```python
frame = ac.seal(b"\xde\xad\xbe\xef", src_mac="aa:bb:cc:dd:ee:ff",
dst_mac="ff:ff:ff:ff:ff:ff", msg_type=0xEE) # zero-padded to 16
يرفع encode المنظّم EncodeError على أي شيء لا يمكن إرساله عبر الشبكة
(قيمة TLV أكبر من 255 بايتًا، نص مشفّر ≥ 0x101)؛ بينما يكون seal متسامحًا بطبيعة تصميمه.
pyrmax.pcap يبحث عن IEs الخاصة ببائع AirMAX داخل إطارات إدارة 802.11،
ويستخرج عناوين MAC من ترويسة 802.11 الخارجية، ويغذّي كل شيء إلى
مفكك الترميز الصحيح. يتم الكشف التلقائي عن كل من .pcap و .pcapng. الإطارات
التي تفشل في فك الترميز (مفتاح خاطئ، بيانات مشوّهة، عنصر IE لبائع غير ذي صلة) يتم تخطيها
بصمت — وتتوقف التكرارات فقط عند نهاية الملف (EOF).```python
from pyrmax import pcap
for meta, packet in pcap.iter_ac("capture.pcap"): print( f"{meta.timestamp:.3f} " f"{meta.src_mac.hex(':')} → {meta.dst_mac.hex(':')} " f"{packet.msg_type.name:<10} " f"radio={packet.radioname!r} ssid={packet.ssid!r}" )
for meta, packet in pcap.iter_m("capture.pcapng"): print( f"{meta.timestamp:.3f} " f"{packet.msg_type.name:<10} " f"src={packet.src_mac.hex(':')} enable={packet.enable}" )
`meta` هو `FrameMeta(timestamp, src_mac, dst_mac, bssid)`. استخراج القناة/مؤشر RSSI من radiotap موجود في قائمة TODO.
### استرجاع اسم الجهاز من IE الخاص بـ Routerboard.com
أطر AirMAX M تكون مصحوبة دائمًا تقريبًا بـ IE بائع من Mikrotik / Routerboard.com (OUI `00:0C:42`) في نفس إطار إدارة 802.11. يحمل الفرعي subtype-1 من IE اسم الجهاز.```python
from pyrmax import pcap
for meta, packet in pcap.iter_routerboard("capture.pcap"):
print(f"{meta.src_mac.hex(':')} → {packet.device_name!r}")
# "AP Sur HY1315"
وحدة فك ترميز Routerboard متاحة أيضًا بشكل مستقل — مرّر بيانات IE ابتداءً من OUI:```python from pyrmax import routerboard
packet = routerboard.decode(ie_data) packet.device_name # "AP Sur HY1315" packet.sub_ies # tuple of SubIe(subtype, data)
قم بربط حزم Routerboard مع حزم M في نفس الالتقاط من خلال
مطابقة `meta.timestamp` + `meta.src_mac`.
### `scan` — ابحث عن الأجهزة الضعيفة
قم بفحص واجهة وضع المراقبة (أو ملف pcap) بحثًا عن أجهزة AirMAX وعلّم
أي منها ضعيف. يتطلب هذا الإضافة `[scan]` (`scapy`) وللالتقاط المباشر،
صلاحيات الجذر.```sh
# offline — scan a capture (no root)
python -m pyrmax scan capture.pcap
# live, passive — read versions only from traffic that happens to fly
sudo python -m pyrmax scan -i wlan0mon --channel 36
# live, active — force AirMAX AC APs to disclose their firmware
sudo python -m pyrmax scan -i wlan0mon --channel 36 --active
تحدد النسخة قابلية التأثر بشكل مختلف حسب النوع. رقما
إصدار متورطان: إصدار بروتوكول تنسيق الإرسال المحمول في
كل AirMAX IE (بما في ذلك الإشارات)، وإصدار البرنامج الثابت للـ AC
(fwname) الذي ينتقل فقط في تبادل الارتباط.```
AirMAX AC ──> proto < 9 ? ──yes──────────────┐
│no ├──> VULNERABLE
▼ │
fw <= 8.7.20 ? ──yes───────────┘
├──no───────> PATCHED
└──unknown──> UNDETERMINED
AirMAX M ───> proto < 15 ? ──yes──> VULNERABLE └──no───────────> UNDETERMINED
نظرًا لأن `proto` موجود في الـ beacon، يتم رصد الأجهزة **القديمة** (AC epoch < 9, M
version < 15) **بشكل سلبي**. يتطلب الوصول إلى حكم `PATCHED` لـ
AC إصدار البرنامج الثابت، لذا يحتاج إما إلى association ملتقطة
أو `--active` (المصافحة النشطة — auth → assoc → قراءة assoc-resp
`fwname` — هي تبادل محطة Ubiquiti كامل ومطابق بايتًا ببايت).
الخيارات: `--channel N` (تثبيت قناة واحدة، وإلا التنقل بين القنوات)، `--seconds N`،
`--cutoff X.Y.Z` (عرضة للثغرة إذا كان `fw <= cutoff`، الافتراضي `8.7.20`)،
`--src MAC` (المصدر للاستقصاء النشط، مثل نظير PTP)، `--vuln-only`،
`--no-set-channel`. رمز الخروج هو **3** عند العثور على أي جهاز عرضة للثغرة
(مفيد للبرمجة النصية)، وإلا يكون `0`.```
AirMAX: 5 device(s) (3 AC, 2 M), 2 vulnerable (AC fw <= 8.7.20 or protocol version below the fixed epoch).
1c:6a:1b:00:00:01 AC VulnAC ch36 v8.7.19 VULNERABLE rssi=-40dBm peers=0
1c:6a:1b:00:00:04 M VulnM ch36 v14 VULNERABLE rssi=-42dBm peers=0
1c:6a:1b:00:00:03 AC PatchedAC ch36 v8.7.24 patched rssi=-41dBm peers=0
1c:6a:1b:00:00:02 AC UndetAC ch36 epoch9 undetermined rssi=-41dBm peers=0
1c:6a:1b:00:00:05 M UndetM ch36 v15 undetermined rssi=-43dBm peers=0
emulate — محاكاة أهداف AirMAXقم ببث Beacon كجهاز واحد أو أكثر من أجهزة AirMAX AC/M الوهمية، وبالنسبة إلى AC، قم بالرد على مصافحة الاكتشاف بحيث يقرأ الماسح النشط إصدار البرنامج الثابت المُحاكى. يتطلب ذلك المكوّن الإضافي [emulate] (scapy)، وواجهة بوضع المراقبة، وصلاحيات الجذر. مفيد لاختبار scan دون أجهزة حقيقية.```sh
sudo python -m pyrmax emulate -i wlan1mon --channel 36
-d ac/8.7.19/VulnAC -d ac/9/UndetAC -d ac/8.7.24/PatchedAC
-d m/14/VulnM -d m/15/UndetM
كل `-d` (قابل للتكرار) هو `TYPE/VERSION[/SSID[/MAC]]`، مفصول بعلامة `/` بحيث تكون النقطتان في MAC آمنتين:
- `ac/8.7.19` — AC حديث، البرنامج الثابت `8.7.19` (الحقبة 9، يكشف `fwname`)
- `ac/9` — حقبة AC الحديثة، **بدون** سلسلة البرنامج الثابت → يرى الماسح
`undetermined`
- `ac/7` — **قديم** AC، حقبة تنسيق الراديو 7 (< 9) → معرّض للخطر، يُكتشف من
المنارة
- `m/14` — AirMAX M، الإصدار 14 (< 15) → معرّض للخطر
- `m/15` — AirMAX M عند الحقبة الثابتة → undetermined
بدون `-d`، تتم محاكاة أسطول تجريبي. `--no-respond` منارات فقط (AC
لن يكشف البرنامج الثابت عندها). إصدار برنامج AC الثابت موجود فقط في
`assoc-resp`، ولهذا السبب يوجد المُجيب.
### اختبار الاثنين معًا على جهاز واحد
`scripts/hwsim_testbed.sh` ينشئ راديين افتراضيين عبر `mac80211_hwsim`
بحيث يمكنك تشغيل `emulate` على أحدهما و`scan` على الآخر دون أي عتاد:```sh
sudo ./scripts/hwsim_testbed.sh up 36 # prints EMU_IFACE / SCAN_IFACE
# ...run emulate on EMU_IFACE and scan on SCAN_IFACE (two terminals)...
sudo ./scripts/hwsim_testbed.sh down
scripts/demo_5_devices.sh يقوم بالعملية كاملة من البداية إلى النهاية — يشغّل
أجهزة الراديو، ويحاكي أسطول الأجهزة الخمسة أعلاه، وينفّذ scan --active، ثم يوقف
كل شيء:```sh
sudo ./scripts/demo_5_devices.sh 36
### معالجة الأخطاء
تؤدي حالات عدم تطابق المخطط وفشل السلامة إلى إثارة `pyrmax.DecodeError`. السبب
الأكثر شيوعًا هو مفتاح خاطئ (عنوان `src_mac` / `dst_mac` خاطئ مُقدَّم إلى
`decode()` للإطار المعني).```python
from pyrmax import ac, DecodeError
try:
packet = ac.decode(data, src_mac=src, dst_mac=dst)
except DecodeError as exc:
print(f"skipping frame: {exc}")
pip install pyrmax[pcap] (أو uv sync --extra pcap) — يجلب dpkt
بحيث يمكن لـ pyrmax.pcap.iter_ac(path) / iter_m(path) بثّ الحزم من
ملفات الالتقاط .pcap أو .pcapng (نوع الرابط DLT_IEEE802_11_RADIO).
يتم اكتشاف الصيغة تلقائيًا من الرقم السحري للملف.pip install pyrmax[live] — يضيف pcapy-ng للالتقاط المباشر من
واجهة لاسلكية بوضع المراقبة. يُستخدم بواسطة خيار -i / --iface في CLI
ومولّد pyrmax.pcap.iter_airmax_live(iface) البرمجي.pip install pyrmax[scan] — يضيف scapy لأمر scan
(الالتقاط/فك التشفير المباشر + مصافحة force-assoc النشطة).pip install pyrmax[emulate] — يضيف لأمر
(حقن إشارات beacon + الرد على مصافحة الاكتشاف كأجهزة مزيفة).ثبّت عدة إضافات معًا، مثل: uv sync --extra scan --extra emulate.
pyrmax/
├── ksy/ # Kaitai Struct source schemas
│ ├── airmax_ac.ksy # AC cleartext outer header (payload decode is hand-written in ac.py)
│ ├── airmax_m.ksy # M outer (OUI marker + encrypted blob)
│ ├── airmax_m_payload.ksy # M decrypted payload (9 documented bytes)
│ └── routerboard.ksy # Mikrotik / Routerboard.com vendor IE
├── src/pyrmax/
│ ├── init.py
│ ├── ac.py # AC decode/encode API + AcPacket dataclass
│ ├── m.py # M decode/encode API + MPacket dataclass
│ ├── routerboard.py # Routerboard IE decoder + RouterboardPacket
│ ├── pcap.py # iter_ac / iter_m / iter_routerboard / iter_airmax / iter_airmax_live
│ ├── devices.py # summarize() — per-device aggregation
│ ├── vuln.py # firmware-version parse + is_vulnerable()
│ ├── scan.py # Scanner — live/pcap discovery + active handshake + vuln verdict
│ ├── emulate.py # Emulator — fake AC/M targets (scapy)
│ ├── main.py # python -m pyrmax CLI
│ ├── exceptions.py
│ ├── _crypto.py # AES-128-ECB + HMAC-SHA1 KDF (internal)
│ └── _generated/ # kaitai-struct-compiler output (committed)
├── scripts/
│ ├── hwsim_testbed.sh # two virtual radios (mac80211_hwsim) for scan<->emulate
│ └── demo_5_devices.sh # end-to-end 5-device emulate + scan demo
└── tests/
├── samples/ # raw frame captures (currently empty)
├── test_ac.py
├── test_m.py
├── test_crypto.py
├── test_pcap.py
├── test_routerboard.py
├── test_devices.py
├── test_cli.py
└── test_integration.py # real-capture round-trips
## التطوير```sh
uv sync # create .venv and install runtime + dev deps
uv run pytest # run tests
uv run ruff check # lint
uv run pyright # static type check
التكوين موجود في pyproject.toml ([tool.pyright]):
typeCheckingMode = "basic" — يكتشف المشاكل البنيوية دون
الاصطدام بحدود dpkt / kaitaistruct / pycryptodome (هذه
الحزم لا توفر كعوب أنواع).src/pyrmax/_generated/ مستبعد — الملفات المولّدة بواسطة Kaitai تحمل
بالفعل # type: ignore وتُستبدل في كل تشغيل
لـ kaitai-struct-compiler.MGMT_Frame.src) يتم عبورها باستخدام ترميز Any على الربط
المحلي بدلاً من تعليقات ignore المتناثرة.ملفات Python المولّدة تحت src/pyrmax/_generated/ مضمّنة في المستودع حتى
تُثبَّت الحزمة دون سلسلة أدوات Kaitai. لإعادة التوليد بعد تعديل ملف
.ksy:```sh
kaitai-struct-compiler -t python --outdir src/pyrmax/_generated/ ksy/*.ksy
field_14field_9csta_field_68ic_6b8enc_tokenversion < 9 مقابل التقاطات حقيقية —
مُنشئ الإرسال (TX builder) يُصدر الإصدار 9 فقط، لذا المسارات ذات الإصدار الأقل (اسم غير موسوم،
وحقول ذيل مفقودة) مطبَّقة وفق المواصفات لكن
غير مُتحقَّق منها على الشبكة.unknown_rest) — معتمة حاليًا.pcap.FrameMeta: القناة، RSSI،
المعدل. حاليًا يتم تعبئة الطابع الزمني/عناوين MAC/BSSID فقط.tests/samples/ — حاليًا:
airmax_ac_beacon.pcap (إطار واحد، beacon) و
airmax_m_probe_response.pcap (إطار واحد، استجابة probe). المزيد من الأنواع
(assoc req/resp، التقاطات متعددة الإطارات) ما زالت مرحّبًا بها.scan (فرض الارتباط النشط force-assoc) وemulate
يحقنان الحزم عبر scapy (إضافتا [scan] / [emulate]). انظر
scan.py / emulate.py.emulate