RFID Kart Okuyucudan Klavye/HID Üzerinden 4 Bayt UID Okuma

RFID Kart Okuyucudan Klavye/HID Üzerinden 4 Bayt UID Okuma

Klavye gibi davranarak sekiz onaltılık karakter gönderen RFID okuyucudan .NET/WinForms ile 4 bayt UID okuma; Türkçe Q/F düzeni, durum makinesi, zaman aşımı, WndProc, klavye kancası ve Raw Input sınırları.

2014'te kartlı geçiş ve otomasyon yazılımları üzerinde çalışırken kullandığım RFID okuyuculardan biri uygulamaya seri port ya da üretici SDK'sı üzerinden veri vermiyordu. Kartı kendi içinde okuyup dört baytlık değeri sekiz onaltılık karakter olarak Windows'a “yazıyordu”. Uygulama açısından cihaz, özel bir RFID protokolü konuşan çevre biriminden çok, oldukça hızlı yazan bir klavye gibi görünüyordu.

Bu ayrım çözümün yönünü tamamen değiştiriyor. İncelenen kod MIFARE kimlik doğrulamasını, ISO/IEC 14443 hava arayüzünü, kart belleğini veya ham USB HID raporlarını çözmüyor. RF tarafındaki işlem okuyucunun içinde bitmiş durumda; benim kodum Windows'a ulaşan 0-9/A-F tuş dizisini sınırlandırılmış bir uygulama çerçevesine dönüştürüyor.

Katmanları bu şekilde ayırıyorum:

RFID kart
   ↓
okuyucu
   ↓
klavye gibi davranan / HID-benzeri giriş
   ↓
Windows klavye iletisi
   ↓
8 onaltılık basamak
   ↓
4 bayt kart değeri
   ↓
otomasyon uygulaması

Sekiz onaltılık basamağı dört baytlık değere dönüştüren aynı çekirdeği Windows Forms'ta dört ayrı giriş sınırında denememin nedeni buydu: normal klavye olayı, IMessageFilter, doğrudan WndProc ve WH_KEYBOARD_LL. Bunlar aynı işi dört kez yazmak için değil, odak ve mesaj akışının gerçek uygulamada nerede kesildiğini görmek için kullanıldı. Bir kiosk formuyla çok pencereli bir otomasyon uygulamasının doğru giriş noktası aynı olmak zorunda değildir.

Önce RFID ile HID kavramını ayırmak gerekir

RFID, kart ile okuyucu arasındaki radyo frekanslı tanımlama teknolojisidir. HID ise bilgisayar tarafındaki giriş aygıtı davranışını anlatır. Bir RFID okuyucu USB üzerinden klavye gibi davranabilir; bu durumda Windows açısından gelen şey çoğu kez "RFID paketi" değil klavye girdisidir.

Klavye gibi davranan okuyucu (keyboard-wedge) modelinin pratik cazibesi dağıtım kolaylığıdır: okuyucu Not Defteri'ne kart numarası yazabiliyorsa WinForms uygulaması da aynı Windows girişini görebilir. Bunun bedeli, insan klavyesi ile okuyucunun aynı giriş zincirine karışabilmesidir.

Ben bu yöntemi ancak cihaz biçiminin sabit, çerçevenin kısa ve uygulama bağlamının kontrollü olduğu durumda tercih ederim. “Bu sekiz tuş hangi fiziksel aygıttan geldi?” sorusunun güvenilir yanıtı gerekiyorsa sıradan klavye iletisi sınırı artık yeterli değildir; Raw Input gibi aygıt kimliği taşıyan bir katmana geçmek gerekir.

Neden tam sekiz karakter?

Tarihsel okuyucu dört baytlık değeri onaltılık metin halinde iletiyordu. Bir bayt iki hex basamağı olduğundan:

4 bayt × bayt başına 2 onaltılık basamak = 8 onaltılık basamak

Sekiz karakterin tamamı geldiğinde 32 bit değer tamamlanır.

Bu, RFID dünyasının genel kuralı değildir. Kart UID'leri ve okuyucu çıktıları farklı uzunluklarda olabilir. Burada sekiz karakterlik çerçeve, kullandığım cihaz ve otomasyon sözleşmesinin özelliğidir. Bir kütüphaneyi "tüm RFID kartları dört bayttır" varsayımıyla yayımlamak teknik olarak yanlış olurdu.

Q ve F klavye sorunu

İlk bakışta cihaz yalnız 0123456789ABCDEF gönderdiği için klavye yerleşiminin önemsiz olduğu düşünülebilir. Rakamlar için büyük ölçüde doğrudur; A-F bölümü için değildir.

Windows giriş zincirinde "tuş", "sanal tuş kodu", "tarama kodu" ve ortaya çıkan karakter aynı kavram değildir. Fiziksel bir tuşun anlamı etkin klavye yerleşimine göre değişebilir. Klavye gibi davranan okuyucu da belirli tuş konumlarını veya sanal tuş kodu dizilerini ürettiğinde, Q klavyede doğru görünen A-F karakterleri F klavyede başka tuş kodlarından gelebilir.

Tarihsel uygulamada bu nedenle iki adet 256 konumlu eşleme tablosu kullandım. Q düzeninde A-F, standart VK_A..VK_F aralığına karşılık gelirken, kullandığım Türkçe F düzeninde gözlemlediğim eşleme şöyleydi:

A -> VK_U
B -> VK_OEM_2
C -> VK_V
D -> VK_E
E -> VK_OEM_1
F -> VK_A

Yayın sürümünde 256 konumlu sabit-zaman eşleme fikrini korudum fakat eşlemeyi uzun bir karakter dizisinin içine saklamadım. Hangi sanal tuş kodunun hangi dört bitlik parçaya dönüştüğü artık açıkça görülebiliyor ve doğrudan test edilebiliyor.

Q/F problemi hangi katmanda ortaya çıkıyorsa düzeltmeyi de orada yapmak gerekir. WM_KEYDOWN seviyesinde elimde nihai karakter değil sanal tuş kodu vardır; önce yanlış karakter üretip sonra metin üzerinde “düzeltme” yapmak yerine sanal tuş kodunu doğrudan doğru dört bitlik parçaya eşlemek daha deterministik bir davranış verir.

Çekirdeği bir sonlu durum makinesi gibi düşünmek

RFID okuyucudan gelen sekiz tuşu bir TextBox içinde biriktirmek mümkündür; fakat otomasyon yazılımında bu gereksiz bir kullanıcı arayüzü bağımlılığı doğurur. Benim için daha temiz model, kısa bir durum makinesiydi.

Yayın sürümünde kabul edilen her onaltılık tuş doğrudan dört bitlik parçaya çevriliyor:

partial = (partial << 4) | nibble

Sekizinci dört bitlik parça geldiğinde partial, doğrudan 32 bit kart değeridir.

Sekiz basamağı önce metin olarak biriktirip sonunda ayrıştırmıyorum. Her geçerli tuş dört bit olarak doğrudan UInt32 içine kaydırılıyor; sekizinci dört bitlik parça geldiğinde eldeki değer zaten hedef 32 bit kart değeridir. Onaltılık metin gerekiyorsa yalnız tüketici TryTakeHex çağırdığında X8 biçimi oluşturulur.

Sık çalışan yol kabaca sabittir:

sanal tuş kodu
→ klavye düzeni eşlemesi
→ dört bitlik parça
→ kaydırma + OR
→ sayaç

Regex, LINQ, büyüyen liste veya tuş başına nesne üretimi yoktur.

Zaman eşiği neden gerekli?

Klavye gibi davranan okuyucu sekiz karakteri insanın yazamayacağı kadar hızlı bir kısa ve hızlı bir dizi halinde gönderir. Kullanıcı ise aynı karakterleri saniyeler içinde yazabilir. Salt hız ölçümünü güvenlik mekanizması olarak görmek doğru değildir, fakat bir çerçeve sınırı olarak yararlıdır.

Tarihsel kodda kullandığım bir saniyelik sınırı yayın sürümünde varsayılan olarak korudum, fakat süre artık kurucu (constructor) üzerinden değiştirilebilir. Zaman ölçümü TimeProvider üzerinden yapıldığı için çerçeve zaman aşımı davranışı testte sistem saatine bağımlı olmak zorunda değildir.

Çerçeve mantığı şöyledir:

ilk dört bitlik parça
   ↓
zaman damgası
   ↓
sonraki dört bitlik parça zamanında geldi mi?
   ├─ evet → devam
   └─ hayır → yarım çerçeveyi at

Bu sınır, yarıda kalan eski bir okumanın birkaç saniye sonra gelen yeni kartla birleşmesini engeller. Ama süreyi kimlik doğrulama kanıtı olarak kullanmıyorum; yalnız ayrıştırıcının çerçeve sınırıdır.

Enter, Tab, Shift, Ctrl gibi okuyucunun gönderebildiği yaygın son ek ve değiştirici tuş olayları çerçeveye eklenmez. Beklenmeyen yazılabilir bir tuş yarım çerçeveyi sıfırlar. Tamamlanan kart ise bekleyen tek bir UInt32 olarak tutulur ve TryTakeCard / TryTakeHex ile bir kez okunup tüketilecek biçimde alınır.

1. Normal KeyDown: en basit yol

Windows Forms'ta ilk seçenek sıradan KeyDown olayıdır.

Avantajı, P/Invoke veya Windows kanca API'si gerektirmemesidir. Form için KeyPreview=true kullanıldığında form, alt kontrollerden önce klavye olaylarını görebilir.

Dezavantajı odaktır. Bazı kontroller belirli tuşları kendi girdi/diyalog işleme mekanizmasında işler. Microsoft belgelerinde de KeyDown, KeyPress, KeyUp sırası ve bazı özel tuşların kontroller tarafından önceden ele alınabileceği belirtilir.

Bu nedenle basit bir kiosk veya kart okutma formunda KeyDown yeterli olabilirken, karmaşık bir masaüstü otomasyonunda daha erken bir mesaj katmanı gerekebilir.

2. IMessageFilter: uygulama mesaj pompasının önünde

IMessageFilter, Windows Forms ileti pompalarında mesaj kontrol veya forma gönderilmeden önce çalışır. Application.AddMessageFilter ile uygulamaya eklenebilir.

Benim kullanımımda bu yaklaşımın avantajı, tek tek her kontrolün KeyDown olayına bağlanmadan uygulama seviyesinde WM_KEYDOWN görebilmesiydi.

Yayın sürümü burada önemli bir sınır koyuyor: mesajı yalnız gözlüyor ve false döndürüyor. Yani kart okuyucu mantığı normal kullanıcı arayüzü davranışını engellemiyor.

Tarihsel kodda aynı filtre içinde WM_ERASEBKGND mesajı da engelleniyordu. Bu RFID alımıyla ilgili değildi ve form çizimini etkileyebilecek bağımsız bir optimizasyon/deneydi. Açık kaynak sürümünden çıkardım. Bir kart okuyucu bileşeninin pencere arka plan çizimini değiştirmesi için teknik gerekçe yok.

Microsoft'un IMessageFilter sözleşmesi tam olarak bu seviyeyi tanımlar: mesaj kontrol veya forma iletilmeden önce incelenebilir; true döndürülürse mesaj durdurulur, false döndürülürse yoluna devam eder.

3. WndProc: Windows mesajına en açık WinForms sınırı

WndProc, WinForms kontrol/form sınıfının Windows WindowProc karşılığıdır. Formun aldığı işletim sistemi mesajlarını doğrudan görmek istediğimde en deterministik sınır budur.

Örnek yapı:

protected override void WndProc(ref Message m)
{
    reader.Observe(ref m);
    base.WndProc(ref m);
}

Burada önemli olan, ele almadığım mesajlar için base.WndProc çağrısını korumaktır. Microsoft da türeyen kontroller için işlenmeyen mesajların temel gerçeklenime aktarılmasını önerir.

WndProc yaklaşımı pencereye özeldir. Bu bazen avantajdır: yalnız belirli form kart okuyucuyu dinler. Bazen dezavantajdır: uygulamanın başka pencereleri veya odak dışı senaryoları kapsam dışı kalır.

4. WH_KEYBOARD_LL: düşük seviyeli klavye kancası

Daha geniş bir giriş sınırı gerektiğinde Windows SetWindowsHookEx ile WH_KEYBOARD_LL kurulabilir. Bu kanca, klavye girdisi iş parçacığı giriş kuyruğuna gönderilmeden önce geri çağırım üretir.

Tarihsel uygulamada kanca yolunu da kullandım. Yayın sürümünü hazırlarken burada “çalışıyor” olmasını yeterli görmeyip yerel Windows API sözleşmesine ve yaşam döngüsüne ilişkin birkaç noktayı düzelttim.

Birincisi, KBDLLHOOKSTRUCT yapısının ilk alanı olan vkCode 32 bittir. Eski kodda Marshal.ReadByte(lParam) kullanılmıştı; yaygın sanal tuş kodu değerleri 0-255 aralığında olduğu için pratikte çalışsa da veri sözleşmesini doğru ifade etmiyordu. Yeni kod Marshal.ReadInt32(lParam) kullanır.

İkincisi, geri çağırım temsilcisi (delegate) nesne alanında tutulur. Microsoft .NET uygulamalarında geri çağırım temsilcisinin GC tarafından toplanmaması gerektiğini özellikle belirtir.

Üçüncüsü, bütün olaylar CallNextHookEx ile zincire aktarılır. Bu bileşen bir tuş kaydedici veya girdi engelleyici değildir. İlgisiz tuşları saklamaz, günlüğe kaydetmez ve diğer uygulamalara giden girdiyi yutmaz.

Dördüncüsü, varsayılan davranış yalnız mevcut süreç ön plandayken veri toplamaktır. Sistem genelinde yakalama teknik olarak mümkün olsa da otomasyon bileşeni için gereksiz kapsam genişletmek doğru değildir. Sistem geneli davranış ancak açıkça istenirse açılır.

Düşük seviyeli kancanın geri çağırımı hızlı olmalıdır. Microsoft, geri çağırım süresi aşılırsa özellikle modern Windows sürümlerinde kancanın sessizce kaldırılabileceğini de belgeliyor. Bu yüzden geri çağırım içinde veri tabanı sorgusu, günlük G/Ç veya kullanıcı arayüzü işlemi yapmak yerine yalnız küçük durum makinesini beslemek daha sağlıklıdır.

Raw Input: fiziksel aygıtı klavyeden ayırmak gerektiğinde

Tarihsel kodum klasik klavye iletisi ve kanca yollarını kullanıyordu. Bugün aynı problem için özellikle birden fazla klavye benzeri cihaz varsa Raw Input'u da değerlendirmek gerekir.

Raw Input'ta uygulama önce ilgilendiği aygıt sınıfını RegisterRawInputDevices ile kaydeder. Ardından WM_INPUT alır; lParam içindeki HRAWINPUT üzerinden GetRawInputData çağrılır. RAWINPUTHEADER.hDevice sayesinde girdinin hangi fiziksel cihazdan geldiği anlaşılabilir.

Klavye gibi davranan RFID okuyucu ile kullanıcının gerçek klavyesini ayırmak için bu çok değerlidir. Kanca veya WM_KEYDOWN tarafında genellikle "hangi klavye" bilgisi uygulamaya doğrudan verilmez.

Raw Input kaydını buna rağmen kütüphanenin içine taşımadım. Windows aynı süreç içinde belirli bir Raw Input aygıt sınıfı için tek hedef pencere kaydı tutar; son kayıt ana uygulamanın önceki düzenini etkileyebilir. Yeniden kullanılabilir bir ayrıştırıcının RegisterRawInputDevices çağrısını gizlice sahiplenmesi bu yüzden doğru sınır değildir. Aygıt kaydı gerekiyorsa ana uygulama bunu kendi pencere mimarisinde açıkça yapmalıdır.

WM_KEYDOWN ile WM_CHAR aynı şey değildir

Bu tür kodlarda en çok karıştırılan noktalardan biri budur.

WM_KEYDOWN / KeyDown tuş kimliğine yakındır. WM_CHAR / KeyPress ise klavye düzeni ve girdi dönüşümü sonrasında ortaya çıkan karaktere yakındır.

Eğer okuyucu zaten doğru karakterleri üretip etkin klavye düzeni sorunu yaşamıyorsa KeyPress ile sekiz karakter toplamak en kolay çözüm olabilir. Fakat Q/F farkını sanal tuş kodu seviyesinde düzeltmek istiyorsam WM_KEYDOWN daha kontrollü bir giriş noktasıdır.

Raw Input ise bir alt katmandadır ve cihaz kimliği avantajı sağlar.

Dolayısıyla "en iyi klavye okuma yöntemi" diye tek cevap yoktur:

| Yöntem | Kapsam | Odak bağımlılığı | Cihaz ayrımı | Karmaşıklık | |---|---|---:|---:|---:| | KeyDown/KeyPress | kontrol/form | yüksek | hayır | düşük | | IMessageFilter | uygulama mesaj pompası | orta | hayır | düşük-orta | | WndProc | belirli pencere | orta | hayır | orta | | WH_KEYBOARD_LL | masaüstü/sistem geneli kanca | düşük | hayır | orta-yüksek | | Raw Input | kayıtlı pencere/aygıt sınıfı | yapılandırılabilir | evet | yüksek |

Kart değerini doğrudan yetki saymamak gerekir

Klavye gibi davranan okuyucu entegrasyonu kullanışlıdır fakat kriptografik bir güven sınırı oluşturmaz. Aynı klavye olayları yazılımla üretilebilir; kullanılan kart teknolojisine bağlı olarak UID kopyalama, öykünme veya yeniden oynatma olasılığı da ayrıca değerlendirilmelidir.

Sekiz karakterlik sonucu bu yüzden:

kart tanımlayıcısı

olarak ele almak daha doğrudur; doğrudan:

kimlik doğrulama kanıtı

olarak değil.

Kartın hangi kullanıcıya ait olduğu, erişim yetkisi, zaman/kapı politikası, anti-passback veya benzeri kurallar otomasyon uygulamasının güvenilir veri ve yetkilendirme katmanında çözülmelidir.

Bu çalışmanın tarihsel yeri

2014'te Afyon Kocatepe Üniversitesi Bilgi İşlem Daire Başkanlığı'ndaki çalışmalarım kartlı geçiş sistemleri, RFID, seri iletişim, veri tabanı ve grafiksel kullanıcı arayüzü yazılımlarını birlikte kapsıyordu. Kişisel arşivimde bu dönem için "RFID Kart Okuyucudan Veri Alma", "RFID Kartlı Güvenlik Yazılımı", UID dönüştürme/kriptolama/oluşturma ve kartlı geçiş veri tabanı yönetimi gibi ayrı çalışmalar yer alıyor.

Kod satırı sayısı küçük olsa da burada birkaç farklı sınır aynı noktada kesişiyor: cihazın gerçekte hangi protokolü uygulamaya sunduğunu doğru tanımlamak, Q/F klavye düzeni farkını sanal tuş kodu seviyesinde ele almak, çerçeveyi zaman sınırıyla ayırmak, Windows mesaj yolunu seçmek ve sonuçta kullanıcı arayüzünden bağımsız dört baytlık bir değer üretmek.

Bugün aynı kodu tekrar tasarlasam da koruyacağım parça bu küçük ayrıştırıcı olurdu. WinForms olayları, kanca kullanımı veya Raw Input entegrasyonu uygulamanın çevresindeki bağdaştırıcılardır; sekiz geçerli dört bitlik parçayı sınırlı durumla tek UInt32 değere dönüştüren çekirdek ise değişmeden kalabilir. HexCardAssembler içindeki kilit de bu durumu tek seferde güncelleyerek aynı nesnenin farklı giriş bağdaştırıcılarından güvenli biçimde kullanılabilmesini sağlar.

Kaynaklar

  • Microsoft, IMessageFilter / PreFilterMessage: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.imessagefilter
  • Microsoft, Control.WndProc: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.wndproc
  • Microsoft, SetWindowsHookEx ve WH_KEYBOARD_LL: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw
  • Microsoft, LowLevelKeyboardProc: https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc
  • Microsoft, Raw Input: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-raw-input
  • Microsoft, WM_INPUT: https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-input
  • Muhammet Ali Köker, Afyon Kocatepe Üniversitesi BİDB çalışma arşivi: https://alikoker.com.tr/aku-bidb-staj
  • Muhammet Ali Köker, proje arşivi: https://alikoker.com.tr/projelerim
  • Muhammet Ali Köker, rfid-keyboard-wedge-csharp: https://github.com/alikoker/rfid-keyboard-wedge-csharp

Açık Kaynak Kod

Makalede incelenen yayın gerçeklenimi açık kaynak olarak GitHub üzerinde yayımlanmıştır:

Kaynak kod: https://github.com/alikoker/rfid-keyboard-wedge-csharp

Depo; HexCardAssembler, Türkçe Q/F sanal tuş eşlemeleri, KeyDown, IMessageFilter, WndProc ve WH_KEYBOARD_LL bağdaştırıcıları ile örnek kullanım ve testleri içerir. Makale donanım-yazılım sınırını ve tasarım kararlarını açıklarken GitHub deposu çalıştırılabilir gerçeklenimi taşır. Kaynak arşivine makale kopyaları gömülmez; depo üst verisi İngilizce kanonik makaleyi reading-rfid-keyboard-wedge-hid-dotnet slug'ı üzerinden referanslar.

Bu sayfanın QR kodu