#include "fiko/cekirdek/lisans_dosya.hpp" #include "fiko/cekirdek/kripto.hpp" #include "fiko/cekirdek/makine_kimlik.hpp" #include "fiko_test.hpp" using namespace fiko; namespace { MenuOge oge(const char* grup, const char* ad, const char* etiket = "", const char* wb = "", const char* kimlik = "") { MenuOge o{grup, ad, "", 0, [](Cekirdek&) {}}; o.lisansEtiketi = etiket; o.workbench = wb; o.kimlik = kimlik; return o; } // Gövdeyi test anahtarıyla imzala → tam lisans metni (imza satırı sonda). struct TestLisans { std::string metin, acikHex; }; TestLisans uret(const std::string& govde) { TestLisans t; std::string ozelHex; if (!kripto::p256AnahtarUret(ozelHex, t.acikHex)) return t; std::string imza; if (!kripto::imzala(lisansKanonikGovde(govde), ozelHex, imza)) return t; t.metin = govde + "imza=" + imza + "\n"; return t; } const char* GOVDE = "# FikoCAD Lisans v1\n" "musteri=Akçalı Döküm\n" "seri=FKC-2026-0001\n" "urun=fikocad\n" "seviye=pro\n" "kisitli=1\n" "grup=Geometri\n" "feature=dm.pushpull\n" "workbench=PartDesign\n" "kota=Geometri/Kutu:2\n" "bitis=2000000000\n"; } // namespace FIKO_TEST(kripto_sha256_bilinen_deger) { u8 d[32]; FIKO_KONTROL(kripto::sha256("abc", 3, d)); FIKO_KONTROL(kripto::baytHex(d, 32) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); } FIKO_TEST(kripto_base64_gidis_donus) { const u8 veri[5] = {1, 2, 3, 250, 255}; const std::string b64 = kripto::base64Kodla(veri, 5); std::vector geri; FIKO_KONTROL(kripto::base64Coz(b64, geri)); FIKO_KONTROL(geri.size() == 5 && geri[3] == 250 && geri[4] == 255); } FIKO_TEST(lisans_dosya_imza_gecerli_ve_alanlar) { const TestLisans t = uret(GOVDE); FIKO_KONTROL(!t.metin.empty()); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(t.metin, t.acikHex, l, hata) == LisansDurum::Gecerli); FIKO_KONTROL(l.seri == "FKC-2026-0001"); FIKO_KONTROL(l.urun == "fikocad"); FIKO_KONTROL(l.kisitli); FIKO_KONTROL(l.gruplar.size() == 1 && l.gruplar[0] == "Geometri"); FIKO_KONTROL(l.featureler.size() == 1 && l.featureler[0] == "dm.pushpull"); FIKO_KONTROL(l.kotalar.size() == 1 && l.kotalar[0].kimlik == "Geometri/Kutu" && l.kotalar[0].limit == 2 && l.kotalar[0].periyot == 0); FIKO_KONTROL(l.bitis == 2000000000); } FIKO_TEST(lisans_dosya_kurcalama_reddedilir) { const TestLisans t = uret(GOVDE); FIKO_KONTROL(!t.metin.empty()); // Tek karakter değiştir (seviye pro→ultra): imza tutmamalı. std::string bozuk = t.metin; const std::size_t p = bozuk.find("seviye=pro"); FIKO_KONTROL(p != std::string::npos); bozuk.replace(p, 10, "seviye=ult"); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(bozuk, t.acikHex, l, hata) == LisansDurum::ImzaGecersiz); // Başka anahtarla da geçmemeli. const TestLisans baska = uret(GOVDE); FIKO_KONTROL(lisansCoz(t.metin, baska.acikHex, l, hata) == LisansDurum::ImzaGecersiz); } // Müşteri lisans dosyasını Notepad'de açıp kaydederse BOM + CRLF girer; yorum satırı // ekleyip silebilir. Kanonik gövde sayesinde imza bunlardan ETKİLENMEZ. FIKO_TEST(lisans_dosya_bom_crlf_yorum_imzayi_bozmaz) { const TestLisans t = uret(GOVDE); FIKO_KONTROL(!t.metin.empty()); LisansBilgi l; std::string hata; std::string bomlu = "\xEF\xBB\xBF" + t.metin; // UTF-8 BOM FIKO_KONTROL(lisansCoz(bomlu, t.acikHex, l, hata) == LisansDurum::Gecerli); std::string crlf; // satır sonları CRLF + baş/son boşluklar for (char c : t.metin) { if (c == '\n') crlf += " \r\n"; else crlf += c; } FIKO_KONTROL(lisansCoz(crlf, t.acikHex, l, hata) == LisansDurum::Gecerli); // Yorum satırı ekle/çıkar → imza bozulmaz (yorum kanonik gövdeye girmez). FIKO_KONTROL(lisansCoz("# elle eklenen not\n" + t.metin, t.acikHex, l, hata) == LisansDurum::Gecerli); // Ama GERÇEK alan değişimi hâlâ yakalanır (kanonikleştirme kalkan değil). std::string bozuk = t.metin; const std::size_t p = bozuk.find("kota=Geometri/Kutu:2"); FIKO_KONTROL(p != std::string::npos); bozuk.replace(p, 21, "kota=Geometri/Kutu:9"); FIKO_KONTROL(lisansCoz(bozuk, t.acikHex, l, hata) == LisansDurum::ImzaGecersiz); } // Ürün dayanıklılığı: bozuk/kesik/çöp lisans dosyası ÇÖKERTMEMELİ — yalnız reddetmeli. // Müşteri dosyayı yanlış kopyalar, disk bozar, e-posta kırpar; uygulama ayakta kalmalı. FIKO_TEST(lisans_dosya_bozuk_girdi_cokmez) { const TestLisans t = uret(GOVDE); LisansBilgi l; std::string hata; // 1) Kesilmiş dosya (e-posta/kopyalama kırpması). GÜVENLİK SÖZLEŞMESİ: gövdenin ya da // imza verisinin herhangi bir parçası eksikse lisans ASLA geçerli olmamalı. // (Sondaki '\n' ve base64 '=' dolgusunun kesilmesi zararsızdır — aynı 64 baytı // çözer, aynı gövdeyi doğrular; onlar bu iddianın dışında.) const std::size_t imzaBas = t.metin.find("imza="); FIKO_KONTROL(imzaBas != std::string::npos); for (std::size_t n = 0; n <= imzaBas + 5; ++n) // gövde + "imza=" etiketi FIKO_KONTROL(lisansCoz(t.metin.substr(0, n), t.acikHex, l, hata) != LisansDurum::Gecerli); // İmza verisinin ortasından kesme de reddedilmeli (dolgu bölgesi hariç). const std::size_t imzaSonu = t.metin.find_last_not_of("=\n\r") + 1; for (std::size_t n = imzaBas + 5; n < imzaSonu; ++n) FIKO_KONTROL(lisansCoz(t.metin.substr(0, n), t.acikHex, l, hata) != LisansDurum::Gecerli); // Sondaki '\n' olmadan geçerli (Notepad "son satırda newline yok" hâli). FIKO_KONTROL(lisansCoz(t.metin.substr(0, t.metin.size() - 1), t.acikHex, l, hata) == LisansDurum::Gecerli); // 2) Tek bayt bozulmaları (deterministik tarama — rastgelelik yok). for (std::size_t i = 0; i < t.metin.size(); i += 11) { std::string bozuk = t.metin; bozuk[i] = (char)(bozuk[i] ^ 0x20); lisansCoz(bozuk, t.acikHex, l, hata); // çökmemeli; sonucu önemli değil } // 3) Yapısal çöp girdiler. const char* copler[] = {"=", "=deger", "anahtar=", "\n\n\n", "###", "imza=", "urun=fikocad\nseri=X\nimza=!!!gecersiz-base64!!!\n", "kota=bozuk\nurun=x\nseri=y\nimza=a\n", "makineTolerans=-5\nurun=x\nseri=y\nimza=a\n", "bitis=99999999999999999999\nurun=x\nseri=y\nimza=a\n"}; for (const char* c : copler) FIKO_KONTROL(lisansCoz(c, t.acikHex, l, hata) != LisansDurum::Gecerli); // 4) Çok uzun satır (10k) — taşma/çökme olmamalı. FIKO_KONTROL(lisansCoz("musteri=" + std::string(10000, 'A') + "\nurun=x\nseri=y\nimza=a\n", t.acikHex, l, hata) != LisansDurum::Gecerli); // 5) Negatif tolerans lisansta geçse bile en az 1'e çekilir (0 ile "hep geçer" olmaz). const TestLisans t2 = uret("urun=fikocad\nseri=X\nmakineTolerans=-5\nmakine=aa\n"); FIKO_KONTROL(lisansCoz(t2.metin, t2.acikHex, l, hata) == LisansDurum::Gecerli); FIKO_KONTROL(l.makineTolerans >= 1); Cekirdek c; LisansSistemi sis; sis.bagla(c); FIKO_KONTROL(lisansUygula(l, sis, {"zz"}, 1900000000) == LisansDurum::MakineUyusmaz); } FIKO_TEST(lisans_dosya_imzasiz_ve_eksik_alan) { LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(GOVDE, "", l, hata) == LisansDurum::ImzaYok); FIKO_KONTROL(lisansCoz("", "", l, hata) == LisansDurum::Bos); FIKO_KONTROL(lisansCoz("urun=fikocad\nimza=x\n", "", l, hata) == LisansDurum::Bicim); } FIKO_TEST(lisans_dosya_politikayi_gate_e_aktarir) { const TestLisans t = uret(GOVDE); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(t.metin, t.acikHex, l, hata) == LisansDurum::Gecerli); Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.suanAyarla(1900000000); // bitiş öncesi FIKO_KONTROL(lisansUygula(l, sis, {}, 1900000000) == LisansDurum::Gecerli); // İzinli grup + izinli feature → çalışır; kota 2 → 3. deneme reddedilir. FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "dm.pushpull", "PartDesign"))); FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "dm.pushpull", "PartDesign"))); FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Kutu", "dm.pushpull", "PartDesign"))); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Kota) == 1); // İzinsiz workbench + izinsiz feature reddedilir. FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Analiz", "dm.pushpull", "Analiz"))); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Workbench) == 1); FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Sim", "cae.sim", "PartDesign"))); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Feature) == 1); } FIKO_TEST(makine_parmak_izi_kararli_ve_ayrisik) { const MakineParmakIzi& p = makineParmakIzi(); // Bu makinede en az 3 alan okunabilmeli (node-lock toleransı 3 için taban). FIKO_KONTROL(p.alanSayisi() >= 3); // Deterministik: ikinci çağrı aynı değerleri verir. FIKO_KONTROL(makineParmakIzi().kurulum == p.kurulum); // Alanlar birbirinden AYRIŞIK (tuz alan adını içeriyor → aynı hash olamaz). const std::vector h = p.hashListesi(); for (std::size_t i = 0; i < h.size(); ++i) { FIKO_KONTROL(h[i].size() == 32); // 32 haneli hex for (std::size_t j = i + 1; j < h.size(); ++j) FIKO_KONTROL(h[i] != h[j]); } // Ham donanım verisi sızmamalı: hash yalnız hex karakter içerir. for (char c : h.front()) FIKO_KONTROL((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')); } FIKO_TEST(makine_parmak_izi_node_lock_ile_calisir) { const std::vector yerel = makineParmakIzi().hashListesi(); LisansBilgi l; l.seri = "FKC-TEST"; l.urun = "fikocad"; l.kisitli = false; l.makineHashleri = yerel; // lisans bu makineye kesildi l.makineTolerans = 3; Cekirdek c; LisansSistemi sis; sis.bagla(c); FIKO_KONTROL(lisansUygula(l, sis, yerel, 1900000000) == LisansDurum::Gecerli); FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu"))); // Tek alan değişirse (disk takası) tolerans hâlâ geçirir. std::vector bozuk = yerel; bozuk[0] = "00000000000000000000000000000000"; Cekirdek c2; LisansSistemi s2; s2.bagla(c2); FIKO_KONTROL(lisansUygula(l, s2, bozuk, 1900000000) == (yerel.size() >= 4 ? LisansDurum::Gecerli : LisansDurum::MakineUyusmaz)); // Tümüyle başka makine → red. Cekirdek c3; LisansSistemi s3; s3.bagla(c3); FIKO_KONTROL(lisansUygula(l, s3, {"aa", "bb", "cc", "dd"}, 1900000000) == LisansDurum::MakineUyusmaz); FIKO_KONTROL(!c3.komutCalistir(oge("Geometri", "Kutu"))); } // Fikri'nin bulduğu boşluk: gizmo sürükleme / sketch aracı / ürün ağacı / renk bandı // komut soyutlamasını ATLAR (doğrudan Cekirdek::uygula) → lisans kapısı görmezdi. namespace { struct SahteIslem : Islem { bool* iz; explicit SahteIslem(bool* i) : iz(i) {} void uygula(Dunya&, OlayGunlugu&) override { *iz = true; } void geriAl(Dunya&, OlayGunlugu&) override { *iz = false; } const char* ad() const override { return "Sahte"; } }; } // namespace FIKO_TEST(lisans_salt_okunur_komut_disi_duzenlemeyi_keser) { Cekirdek c; LisansSistemi sis; sis.bagla(c); // Varsayılan: kapı yok → doğrudan düzenleme serbest (bugünkü davranış birebir). bool iz = false; FIKO_KONTROL(c.uygula(std::make_unique(&iz))); FIKO_KONTROL(iz); // Görüntüleyici kipi: komut DIŞI doğrudan düzenleme reddedilir (gizmo/sketch/ağaç). sis.saltOkunurAyarla(true); iz = false; FIKO_KONTROL(!c.uygula(std::make_unique(&iz))); FIKO_KONTROL(!iz); // sahne DEĞİŞMEDİ // Ama gate'i GEÇEN bir komut kendi İşlem'ini uygulayabilir (STEP İçe Aktar gibi): // komut kapsamı içinde düzenleme kapısı sorulmaz. sis.kipAyarla(LisansSistemi::Kip::Kisitli); sis.grupIzinVer("Dosya"); bool komutIci = false; MenuOge o{"Dosya", "İçe Aktar", "", 0, [&](Cekirdek& ck) { ck.uygula(std::make_unique(&komutIci)); }}; FIKO_KONTROL(c.komutCalistir(o)); FIKO_KONTROL(komutIci); // izinli komutun içindeki düzenleme ÇALIŞTI // Lisanssız grubun komutu ise zaten kapıda durur. bool yasak = false; MenuOge o2{"Değiştir", "Yüz Taşı", "", 0, [&](Cekirdek& ck) { ck.uygula(std::make_unique(&yasak)); }}; FIKO_KONTROL(!c.komutCalistir(o2)); FIKO_KONTROL(!yasak); } // Görüntüleyici kipi sözleşmesi (lisans_yukle.cpp'deki IZINLI listesinin ikizi). // STEP/APT/mesh içe-dışa aktarma ÜCRETLİDİR; kendi .fikocad'ını açıp kaydetme ve // lisans penceresine erişim HER ZAMAN açık kalmalı (kullanıcı kendini kilitleyemez). FIKO_TEST(lisans_goruntuleyici_kipi_step_ucretli_lisans_penceresi_acik) { Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.kipAyarla(LisansSistemi::Kip::Kisitli); for (const char* g : {"Dosya", "Görünüm", "Seçim", "Ölçüm", "Araçlar", "Yardım"}) sis.grupIzinVer(g); for (const char* k : {"dosya.yeni", "dosya.ac", "dosya.kaydet", "dosya.olay_gunluk", "gorunum.*", "secim.*", "olcum.*", "arac.komut_denetim", "arac.ayarlar", "arac.guncelleme", "arac.gecici_temizle", "arac.geri_bildirim"}) sis.komutIzinVer(k); sis.saltOkunurAyarla(true); auto k = [&](const char* grup, const char* ad, const char* kimlik) { return c.komutCalistir(oge(grup, ad, "", "", kimlik)); }; // ÜCRETLİ: STEP ve diğer içe/dışa aktarımlar kapalı. FIKO_KONTROL(!k("Dosya", "STEP İçe Aktar...", "dosya.step_ice")); FIKO_KONTROL(!k("Dosya", "STEP Dışa Aktar...", "dosya.step_disa")); FIKO_KONTROL(!k("Dosya", "Dışa Aktar", "dosya.mesh_disa")); FIKO_KONTROL(!k("Dosya", "APT Aç...", "dosya.apt_ac")); FIKO_KONTROL(!k("Dosya", "Web CAD Aç...", "dosya.webcad_ac")); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Komut) == 5); // Görünürlük de aynı beyaz listeyi izlemeli: ücretli komut gösterilmez. FIKO_KONTROL(!sis.ogeGorunur("dosya.step_ice", "Dosya")); FIKO_KONTROL(!sis.ogeGorunur("geometri.kutu", "Geometri")); // AÇIK: kendi çalışmanı açıp kaydet (veri rehin kalmaz) + incele/ölç. FIKO_KONTROL(k("Dosya", "Yeni Belge", "dosya.yeni")); FIKO_KONTROL(k("Dosya", "Aç (.fikocad)...", "dosya.ac")); FIKO_KONTROL(k("Dosya", "Kaydet (.fikocad)...", "dosya.kaydet")); FIKO_KONTROL(k("Görünüm", "İzometrik", "gorunum.izometrik")); FIKO_KONTROL(k("Seçim", "Yüz kipi", "secim.kip_yuz")); FIKO_KONTROL(k("Ölçüm", "Kumpas", "olcum.kumpas")); // ⚠️ KİLİTLENME KORUMASI: lisans penceresi ve ayarlar HER ZAMAN açılabilmeli. FIKO_KONTROL(k("Araçlar", "🔒 Komut Denetim", "arac.komut_denetim")); FIKO_KONTROL(k("Araçlar", "⚙ Ayarlar…", "arac.ayarlar")); FIKO_KONTROL(sis.ogeGorunur("dosya.kaydet", "Dosya")); FIKO_KONTROL(sis.ogeGorunur("arac.ayarlar", "Araçlar")); // Satılabilir araçlar (WTEC kafa) joker ile SIZMAMALI. FIKO_KONTROL(!k("Araçlar", "🔧 FikoYT Diagonal Kafa", "arac.diagonal_kafa")); FIKO_KONTROL(!k("Araçlar", "🔩 FikoYT Ozmo Kafa", "arac.ozmo_kafa")); // Modelleme kapalı (grup + komut + salt-okunur üç katman). FIKO_KONTROL(!k("Geometri", "Kutu", "geometri.kutu")); FIKO_KONTROL(!k("Değiştir", "Yüz İt/Çek", "dm.yuz_itcek")); FIKO_KONTROL(!sis.duzenlemeIzinli()); } // Satın alınmayan komut UI'da HİÇ görünmemeli. MenuKaydi görünürlük kapısı mevcut // `etkinMi` sözleşmesini sarmalar → toolbar/şerit/palet/radyal menü otomatik gizler. FIKO_TEST(lisans_gorunurluk_kapisi_lisanssiz_komutu_gizler) { Cekirdek c; LisansSistemi sis; sis.bagla(c); c.menuler().gorunurlukKapisiAyarla( [&sis](const std::string& k, const std::string& g) { return sis.ogeGorunur(k, g); }, [&sis] { return sis.politikaSurumu(); }); bool ozelEtkin = true; // komutun KENDİ bağlam koşulu (lisanstan bağımsız) MenuOge kutu = oge("Geometri", "Kutu", "", "", "geometri.kutu"); MenuOge silindir = oge("Geometri", "Silindir", "", "", "geometri.silindir"); MenuOge denetim = oge("Araçlar", "🔒 Komut Denetim", "", "", "arac.komut_denetim"); MenuOge kosullu = oge("Geometri", "Koşullu", "", "", "geometri.kosullu"); kosullu.etkinMi = [&ozelEtkin](Cekirdek&) { return ozelEtkin; }; for (const MenuOge& o : {kutu, silindir, denetim, kosullu}) c.menuler().ekle(o); // Görünürlük = komut UI listesinde (hepsi()) var mı. Lisans `etkinMi`'ye // BİNDİRİLMEZ: bindirilse UI "katı gövde gerekli" gibi YANLIŞ gerekçe gösterirdi. auto gorunur = [&](const char* ad) { for (const MenuOge& o : c.menuler().hepsi()) if (o.ad == ad) return !o.etkinMi || o.etkinMi(c); return false; }; // Kilitli komut tam kayıtta DURUR (lisans paneli "satın alınabilir" listesi + katalog). auto kayitliMi = [&](const char* ad) { for (const MenuOge& o : c.menuler().tumKayit()) if (o.ad == ad) return true; return false; }; // Politika boşken hepsi görünür (mevcut davranış birebir). FIKO_KONTROL(gorunur("Kutu") && gorunur("Silindir") && gorunur("🔒 Komut Denetim")); // Yalnız geometri.kutu + lisans penceresi satın alındı. sis.komutIzinVer("geometri.kutu"); sis.komutIzinVer("arac.komut_denetim"); FIKO_KONTROL(gorunur("Kutu")); FIKO_KONTROL(!gorunur("Silindir")); // satın alınmadı → GİZLİ FIKO_KONTROL(kayitliMi("Silindir")); // ama satılabilir listede DURUYOR // Kilitli komutun kendi etkinMi'si BOZULMAMALI (yanlış gerekçe kaynağı buydu). for (const MenuOge& m : c.menuler().tumKayit()) if (m.ad == "Silindir") FIKO_KONTROL(!m.etkinMi); // sarmalanmadı, olduğu gibi FIKO_KONTROL(gorunur("🔒 Komut Denetim")); // lisans penceresi HER ZAMAN görünür // Komutun kendi etkinMi koşulu KORUNUR (lisans onu ezmez, VE'lenir). sis.komutIzinVer("geometri.kosullu"); FIKO_KONTROL(gorunur("Koşullu")); ozelEtkin = false; FIKO_KONTROL(!gorunur("Koşullu")); // bağlam kapalı → yine gizli ozelEtkin = true; // Kısıtlı kipte grup whitelist'i de görünürlüğü etkiler. sis.kipAyarla(LisansSistemi::Kip::Kisitli); FIKO_KONTROL(!gorunur("Kutu")); // "Geometri" grubu izinli değil sis.grupIzinVer("Geometri"); FIKO_KONTROL(gorunur("Kutu")); } // 🚨 Süresi dolmuş lisansta politika SIFIRLANMAZSA komut gate'i reddederken gizmo/ // sketch/ağaç düzenlemesi açık kalır (yaşandı). lisansUygula SuresiDolmus döndüğünde // çağıranın görüntüleyici kipini uygulaması ZORUNLU — bu test o sözleşmeyi kilitler. FIKO_TEST(lisans_suresi_dolmus_duzenlemeyi_de_kapatmali) { LisansBilgi l; l.seri = "FKC-SURE"; l.urun = "fikocad"; l.kisitli = false; l.bitis = 1000; // çok eski Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.suanAyarla(2000); FIKO_KONTROL(lisansUygula(l, sis, {}, 2000) == LisansDurum::SuresiDolmus); // Komut gate'i zaten reddeder (expiry). FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Expiry) >= 1); // ⚠️ AMA doğrudan düzenleme (gizmo/sketch/ağaç) HÂLÂ AÇIK — lisansUygula tek başına // yetmez; çağıran görüntüleyici kipini uygulamalı. Kusurun kanıtı: FIKO_KONTROL(sis.duzenlemeIzinli()); // Çağıranın yapması gereken (lisans_yukle.cpp: goruntuleyiciKipiUygula): sis.saltOkunurAyarla(true); FIKO_KONTROL(!sis.duzenlemeIzinli()); bool iz = false; FIKO_KONTROL(!c.uygula(std::make_unique(&iz))); FIKO_KONTROL(!iz); } // Süre bitmeden çalışır; bitince tüm haklar kapanır (sınır davranışı). FIKO_TEST(lisans_expiry_sinir_davranisi) { LisansBilgi l; l.seri = "FKC-SINIR"; l.urun = "fikocad"; l.kisitli = false; l.bitis = 5000; Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.suanAyarla(4999); // bitişten 1 sn önce FIKO_KONTROL(lisansUygula(l, sis, {}, 4999) == LisansDurum::Gecerli); FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); sis.suanAyarla(5000); // tam bitiş anı → hâlâ geçerli (suan > bitis şartı) FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); sis.suanAyarla(5001); // bitişten 1 sn sonra → red FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); // Süresiz lisans (bitis=0) zaman ne olursa olsun çalışır. LisansBilgi suresiz = l; suresiz.bitis = 0; Cekirdek c2; LisansSistemi s2; s2.bagla(c2); s2.suanAyarla(9999999999); FIKO_KONTROL(lisansUygula(suresiz, s2, {}, 9999999999) == LisansDurum::Gecerli); FIKO_KONTROL(c2.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); } // ── PAKET + DÖNEMSEL KOTA (tam kapsamlı satış motoru) ────────────────────── // Aynı lisansta: farklı bitişli paketler, paket başına kota, dönem sıfırlaması. FIKO_TEST(lisans_paket_ayri_bitis_ve_donemsel_kota) { const TestLisans t = uret( "musteri=Akçalı\n" "seri=FKC-PAKET\n" "urun=fikocad\n" "kisitli=0\n" "paket=modelleme\n" "paket.ad=Modelleme Yıllık\n" "paket.bitis=2000000000\n" "komut=geometri.*\n" "komut=dm.*\n" "paket=cam\n" "paket.ad=CAM 3 Aylık\n" "paket.bitis=1900000000\n" "komut=cam.*\n" "kota=cam.gkod:3/ay\n" "kota=cam.simulasyon:2/gun\n"); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(t.metin, t.acikHex, l, hata) == LisansDurum::Gecerli); FIKO_KONTROL(l.paketler.size() == 2); FIKO_KONTROL(l.paketler[0].ad == "Modelleme Yıllık" && l.paketler[0].komutlar.size() == 2); FIKO_KONTROL(l.paketler[1].bitis == 1900000000 && l.paketler[1].kotalar.size() == 2); FIKO_KONTROL(l.paketler[1].kotalar[0].limit == 3 && l.paketler[1].kotalar[0].periyot == 2592000); // "ay" = 30 gün FIKO_KONTROL(l.paketler[1].kotalar[1].periyot == 86400); // "gun" Cekirdek c; LisansSistemi sis; sis.bagla(c); const i64 T0 = 1800000000; // her iki paket de geçerli sis.suanAyarla(T0); FIKO_KONTROL(lisansUygula(l, sis, {}, T0) == LisansDurum::Gecerli); auto k = [&](const char* kimlik) { return c.komutCalistir(oge("X", kimlik, "", "", kimlik)); }; // Her iki paket de çalışır. FIKO_KONTROL(k("geometri.kutu")); FIKO_KONTROL(k("cam.profil")); // Paket dışı komut → hak yok. FIKO_KONTROL(!k("pafta.olustur")); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Komut) == 1); // Aylık kota 3: dördüncü G-kod reddedilir. FIKO_KONTROL(k("cam.gkod") && k("cam.gkod") && k("cam.gkod")); FIKO_KONTROL(!k("cam.gkod")); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Kota) == 1); // Günlük kota 2 (ayrı kova — G-kod kullanımı simülasyonu etkilemez). FIKO_KONTROL(k("cam.simulasyon") && k("cam.simulasyon")); FIKO_KONTROL(!k("cam.simulasyon")); // ── Dönem sıfırlaması: ertesi gün simülasyon hakkı yenilenir, AY kotası yenilenmez. sis.suanAyarla(T0 + 86400); FIKO_KONTROL(k("cam.simulasyon")); // yeni gün → sayaç sıfır FIKO_KONTROL(!k("cam.gkod")); // hâlâ aynı ay → kota dolu // ── Bir ay sonra aylık kota da yenilenir. sis.suanAyarla(T0 + 31 * 86400); FIKO_KONTROL(k("cam.gkod")); // ── CAM paketinin süresi dolunca: CAM kapanır, modelleme devam eder. sis.suanAyarla(1950000000); // cam.bitis geçti, modelleme.bitis geçmedi FIKO_KONTROL(!k("cam.profil")); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Expiry) >= 1); FIKO_KONTROL(k("geometri.kutu")); // modelleme paketi hâlâ geçerli FIKO_KONTROL(k("dm.yuz_itcek")); // ── Modelleme de dolunca her şey kapanır. sis.suanAyarla(2100000000); FIKO_KONTROL(!k("geometri.kutu")); } // Kota sayaçları oturumlar arası KALICI olmalı: yoksa kullanıcı uygulamayı kapatıp // açarak "ayda 100" hakkını sonsuz tazeler. FIKO_TEST(lisans_kota_sayaci_oturumlar_arasi_kalici) { Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.suanAyarla(1800000000); sis.komutIzinVer("cam.*"); sis.kotaAyarla("cam.gkod", 2, kotaPeriyotCoz("ay")); auto k = [&] { return c.komutCalistir(oge("CAM", "G-Kod", "", "", "cam.gkod")); }; FIKO_KONTROL(k()); // 1. çalışır FIKO_KONTROL(k()); // 2. çalışır FIKO_KONTROL(!k()); // 3. kota dolu const std::string kayit = sis.kotaSerialize(); FIKO_KONTROL(!kayit.empty()); // "Uygulama yeniden açıldı": yeni sistem, aynı politika + kaydedilmiş sayaç. Cekirdek c2; LisansSistemi s2; s2.bagla(c2); s2.suanAyarla(1800000000); s2.komutIzinVer("cam.*"); s2.kotaAyarla("cam.gkod", 2, kotaPeriyotCoz("ay")); s2.kotaYukle(kayit); FIKO_KONTROL(!c2.komutCalistir(oge("CAM", "G-Kod", "", "", "cam.gkod"))); // hak yok // Ay değişince yenilenir. s2.suanAyarla(1800000000 + 31 * 86400); FIKO_KONTROL(c2.komutCalistir(oge("CAM", "G-Kod", "", "", "cam.gkod"))); } // Fikri'nin senaryosu (yapay zeka limitleri gibi): AYNI komuta iki pencere — // "5 saatte 5 kullanım" VE "haftada toplam 30". İkisi de bağımsız kova, ikisi de // sağlanmalı; kısa pencere sık yenilenir, uzun pencere tavanı korur. FIKO_TEST(lisans_kota_cok_pencereli_5saatte5_haftada30) { // Periyot ayrıştırma sözleşmesi. FIKO_KONTROL(kotaPeriyotCoz("5saat") == 5 * 3600); FIKO_KONTROL(kotaPeriyotCoz("30dk") == 1800); FIKO_KONTROL(kotaPeriyotCoz("hafta") == 604800); FIKO_KONTROL(kotaPeriyotCoz("2gun") == 172800); FIKO_KONTROL(kotaPeriyotCoz("") == 0 && kotaPeriyotCoz("toplam") == 0); FIKO_KONTROL(kotaPeriyotMetni(18000) == "5 saat"); FIKO_KONTROL(kotaPeriyotMetni(604800) == "hafta"); FIKO_KONTROL(kotaPeriyotMetni(0) == "ömür boyu"); Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.komutIzinVer("yz.*"); sis.kotaAyarla("yz.sorgu", 5, kotaPeriyotCoz("5saat")); sis.kotaAyarla("yz.sorgu", 30, kotaPeriyotCoz("hafta")); const i64 T0 = 1800000000; sis.suanAyarla(T0); auto k = [&] { return c.komutCalistir(oge("YZ", "Sorgu", "", "", "yz.sorgu")); }; // İlk 5 kullanım geçer, 6. reddedilir (kısa pencere). for (int i = 0; i < 5; ++i) FIKO_KONTROL(k()); FIKO_KONTROL(!k()); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Kota) == 1); // 4 saat sonra hâlâ AYNI 5 saatlik pencere → kapalı. sis.suanAyarla(T0 + 4 * 3600); FIKO_KONTROL(!k()); // 5 saat dolunca kısa pencere yenilenir; haftalık kova birikmeye devam eder. sis.suanAyarla(T0 + 5 * 3600 + 1); for (int i = 0; i < 5; ++i) FIKO_KONTROL(k()); // toplam 10 FIKO_KONTROL(!k()); // Haftalık tavana yürü: her 5 saatte 5 → 30'a ulaşınca hafta kovası kapatır. i64 t = T0 + 5 * 3600 + 1; int toplam = 10; while (toplam < 30) { t += 5 * 3600 + 1; sis.suanAyarla(t); for (int i = 0; i < 5 && toplam < 30; ++i) { FIKO_KONTROL(k()); ++toplam; } } // 30 doldu: kısa pencere taze olsa BİLE haftalık kova reddeder. t += 5 * 3600 + 1; sis.suanAyarla(t); FIKO_KONTROL(!k()); // Hafta dolunca ikisi de yenilenir. sis.suanAyarla(T0 + 604800 + 10); FIKO_KONTROL(k()); } FIKO_TEST(lisans_muhur_bellek_kurcalamasini_yakalar) { const TestLisans t = uret(GOVDE); LisansBilgi l; std::string hata; lisansCoz(t.metin, t.acikHex, l, hata); Cekirdek c; LisansSistemi sis; sis.bagla(c); sis.suanAyarla(1900000000); FIKO_KONTROL(lisansUygula(l, sis, {}, 1900000000) == LisansDurum::Gecerli); FIKO_KONTROL(sis.muhurGecerli()); FIKO_KONTROL(sis.saglikKontrol()); // "Crack": bellekten politikayı genişlet (sınırsız komut hakkı ver). sis.komutIzinVer("*"); FIKO_KONTROL(!sis.muhurGecerli()); // Sağlık kapısı politikayı en kısıtlı hâle indirir (sessiz yanlış davranış yok). FIKO_KONTROL(!sis.saglikKontrol()); FIKO_KONTROL(sis.kip() == LisansSistemi::Kip::Kisitli); FIKO_KONTROL(sis.grupIzinli("Dosya")); // kaydedip çıkabilsin FIKO_KONTROL(!sis.grupIzinli("Geometri")); // hile kazanç sağlamadı // Yeni durum mühürlenir → aynı red tekrar tekrar tetiklenmez. FIKO_KONTROL(sis.saglikKontrol()); // Mühürsüz sistem (lisans yok) hiç etkilenmez — bugünkü davranış korunur. LisansSistemi bos; FIKO_KONTROL(bos.muhurGecerli()); FIKO_KONTROL(bos.saglikKontrol()); bos.komutIzinVer("*"); FIKO_KONTROL(bos.saglikKontrol()); } FIKO_TEST(lisans_yapi_kilidi_yamali_exe_reddeder) { // Yerel exe hash'i ile kesilen lisans geçer; başka bir yapı hash'i reddedilir. const std::string yerel = uygulamaYapiHash(); FIKO_KONTROL(yerel.size() == 32); // test exe'sinin kendi sha256'sı LisansBilgi l; l.seri = "FKC-YAPI"; l.urun = "fikocad"; l.kisitli = false; l.yapiHash = yerel; Cekirdek c; LisansSistemi sis; sis.bagla(c); FIKO_KONTROL(lisansUygula(l, sis, {}, 1900000000) == LisansDurum::Gecerli); FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu"))); l.yapiHash = "ffffffffffffffffffffffffffffffff"; // yamalı/başka ikili Cekirdek c2; LisansSistemi s2; s2.bagla(c2); FIKO_KONTROL(lisansUygula(l, s2, {}, 1900000000) == LisansDurum::YapiUyusmaz); FIKO_KONTROL(!c2.komutCalistir(oge("Geometri", "Kutu"))); } FIKO_TEST(lisans_komut_bazli_tek_tek_satis) { Cekirdek c; LisansSistemi sis; sis.bagla(c); // Boş liste = gate yok (geri uyum: eski lisanslar aynen çalışır). FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); // Tek tek satın alınan komutlar + grup jokeri. sis.komutIzinVer("geometri.kutu"); sis.komutIzinVer("cam.*"); FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); FIKO_KONTROL(c.komutCalistir(oge("CAM", "Kontur", "", "", "cam.kontur"))); FIKO_KONTROL(c.komutCalistir(oge("CAM", "Finiş 3B", "", "", "cam.finis3d"))); // Satın alınmayan komut → red (jest/palet/menü fark etmez, tek merkez). FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Silindir", "", "", "geometri.silindir"))); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Komut) == 1); // Kimliksiz komut → "Grup/Ad" fallback + "Grup/*" jokeri. FIKO_KONTROL(!c.komutCalistir(oge("Ölçüm", "Mesafe"))); sis.komutIzinVer("Ölçüm/*"); FIKO_KONTROL(c.komutCalistir(oge("Ölçüm", "Mesafe"))); sis.komutIzinVer("*"); // sınırsız paket FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Silindir", "", "", "geometri.silindir"))); } FIKO_TEST(lisans_dosya_komut_satiri_ve_katalog) { const TestLisans t = uret( "seri=FKC-2026-0002\n" "urun=fikocad\n" "kisitli=0\n" "komut=geometri.kutu\n" "komut=cam.*\n"); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(t.metin, t.acikHex, l, hata) == LisansDurum::Gecerli); FIKO_KONTROL(l.komutlar.size() == 2); Cekirdek c; LisansSistemi sis; sis.bagla(c); FIKO_KONTROL(lisansUygula(l, sis, {}, 1900000000) == LisansDurum::Gecerli); FIKO_KONTROL(c.komutCalistir(oge("Geometri", "Kutu", "", "", "geometri.kutu"))); FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Silindir", "", "", "geometri.silindir"))); // Katalog CSV: kayıtlı her komut bir satır (fiyat sütunu boş → sen doldurursun). c.menuler().ekle(oge("Geometri", "Kutu", "", "PartDesign", "geometri.kutu")); c.menuler().ekle(oge("Ölçüm", "Mesafe")); const std::string csv = lisansKatalogCsv(c.menuler()); FIKO_KONTROL(csv.find("kimlik;grup;ad;workbench;altGrup;fiyat") == 0); FIKO_KONTROL(csv.find("geometri.kutu;Geometri;Kutu;PartDesign;;") != std::string::npos); FIKO_KONTROL(csv.find("Ölçüm/Mesafe;Ölçüm;Mesafe;;;") != std::string::npos); } FIKO_TEST(lisans_dosya_expiry_ve_node_lock) { const TestLisans t = uret(GOVDE); LisansBilgi l; std::string hata; lisansCoz(t.metin, t.acikHex, l, hata); Cekirdek c; LisansSistemi sis; sis.bagla(c); // Süre dolmuş. sis.suanAyarla(2000000001); FIKO_KONTROL(lisansUygula(l, sis, {}, 2000000001) == LisansDurum::SuresiDolmus); FIKO_KONTROL(!c.komutCalistir(oge("Geometri", "Kutu", "dm.pushpull", "PartDesign"))); FIKO_KONTROL(sis.redSayaci(LisansSistemi::RedSebep::Expiry) == 1); // Node-lock toleransı: 4 alandan 3'ü eşleşirse geçer. l.makineHashleri = {"aa", "bb", "cc", "dd"}; l.makineTolerans = 3; Cekirdek c2; LisansSistemi s2; s2.bagla(c2); s2.suanAyarla(1900000000); FIKO_KONTROL(lisansUygula(l, s2, {"aa", "bb", "cc", "zz"}, 1900000000) == LisansDurum::Gecerli); FIKO_KONTROL(c2.komutCalistir(oge("Geometri", "Kutu", "dm.pushpull", "PartDesign"))); Cekirdek c3; LisansSistemi s3; s3.bagla(c3); s3.suanAyarla(1900000000); FIKO_KONTROL(lisansUygula(l, s3, {"aa", "bb", "yy", "zz"}, 1900000000) == LisansDurum::MakineUyusmaz); FIKO_KONTROL(!c3.komutCalistir(oge("Geometri", "Kutu", "dm.pushpull", "PartDesign"))); FIKO_KONTROL(s3.redSayaci(LisansSistemi::RedSebep::NodeLock) == 1); } // ── Zarf (gizlilik) katmanı ───────────────────────────────────────────────── FIKO_TEST(lisans_aes_gcm_gidis_donus) { u8 anahtar[32]; lisansZarfAnahtari(anahtar); const std::string duz = "musteri=Akçalı Döküm\nkomut=cam.*\nkota=cam.gkod:100/ay\n"; std::string zarf; FIKO_KONTROL(kripto::aesGcmSifrele(duz, anahtar, zarf)); FIKO_KONTROL(zarf.size() > duz.size()); FIKO_KONTROL(zarf.compare(0, 5, "FKLE1") == 0); // Düz metin şifreli gövdede GÖRÜNMEMELİ (gizliliğin bütün amacı bu). FIKO_KONTROL(zarf.find("cam.gkod") == std::string::npos); std::string cozulen; FIKO_KONTROL(kripto::aesGcmCoz(zarf, anahtar, cozulen)); FIKO_KONTROL(cozulen == duz); // Nonce her çağrıda taze → aynı metin iki kez şifrelenince çıktı FARKLI olur. std::string zarf2; FIKO_KONTROL(kripto::aesGcmSifrele(duz, anahtar, zarf2)); FIKO_KONTROL(zarf2 != zarf); } FIKO_TEST(lisans_aes_gcm_yanlis_anahtar_ve_bozulma) { u8 anahtar[32]; lisansZarfAnahtari(anahtar); const std::string duz = "seri=FKC-2026-0001\nkomut=*\n"; std::string zarf; FIKO_KONTROL(kripto::aesGcmSifrele(duz, anahtar, zarf)); // Yanlış anahtar → GCM etiketi tutmaz, çözme başarısız ve çıktı BOŞ. u8 yanlis[32]; for (int i = 0; i < 32; ++i) yanlis[i] = (u8)(anahtar[i] ^ 0x5A); std::string cikti = "kirli"; FIKO_KONTROL(!kripto::aesGcmCoz(zarf, yanlis, cikti)); FIKO_KONTROL(cikti.empty()); // Tek bayt bozulması (şifreli gövdede) → etiket tutmaz. std::string bozuk = zarf; bozuk[bozuk.size() - 1] = (char)(bozuk[bozuk.size() - 1] ^ 0x01); FIKO_KONTROL(!kripto::aesGcmCoz(bozuk, anahtar, cikti)); // Nonce'un bozulması da aynı sonucu verir. std::string bozukNonce = zarf; bozukNonce[6] = (char)(bozukNonce[6] ^ 0x01); FIKO_KONTROL(!kripto::aesGcmCoz(bozukNonce, anahtar, cikti)); // Sihirli imza yoksa zarf değildir → reddedilir. std::string sihirsiz = zarf; sihirsiz[0] = 'X'; FIKO_KONTROL(!kripto::aesGcmCoz(sihirsiz, anahtar, cikti)); } FIKO_TEST(lisans_zarf_gecerli_lisansi_tasir) { const TestLisans t = uret(GOVDE); FIKO_KONTROL(!t.metin.empty()); u8 anahtar[32]; lisansZarfAnahtari(anahtar); const std::string zarfli = lisansZarfla(t.metin, anahtar); FIKO_KONTROL(!zarfli.empty()); FIKO_KONTROL(zarfli.compare(0, 5, "FKLE1") == 0); // Müşteri Notepad'de açınca hiçbir alanı okuyamamalı. FIKO_KONTROL(zarfli.find("musteri=") == std::string::npos); FIKO_KONTROL(zarfli.find("FKC-2026-0001") == std::string::npos); // zarfCoz → lisansCoz zinciri: imza hâlâ doğrulanır. const std::string acilan = lisansZarfCoz(zarfli, anahtar); FIKO_KONTROL(acilan == t.metin); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(acilan, t.acikHex, l, hata) == LisansDurum::Gecerli); FIKO_KONTROL(l.seri == "FKC-2026-0001"); // Zarf metninde base64 gövdesi bozulursa açılmaz (boş döner). std::string bozuk = zarfli; const std::size_t nl = bozuk.find('\n'); bozuk[nl + 3] = (bozuk[nl + 3] == 'A') ? 'B' : 'A'; const std::string bozukAcilan = lisansZarfCoz(bozuk, anahtar); FIKO_KONTROL(bozukAcilan.empty()); } FIKO_TEST(lisans_zarf_duz_metin_geriye_uyum) { const TestLisans t = uret(GOVDE); u8 anahtar[32]; lisansZarfAnahtari(anahtar); // FKLE1 ile başlamayan içerik OLDUĞU GİBİ geçer — sahadaki eski düz metin // .fkl dosyaları güncelleme sonrası da çalışmalı. const std::string aynen = lisansZarfCoz(t.metin, anahtar); FIKO_KONTROL(aynen == t.metin); LisansBilgi l; std::string hata; FIKO_KONTROL(lisansCoz(aynen, t.acikHex, l, hata) == LisansDurum::Gecerli); // BOM'lu düz metin de aynen geçer (Notepad kaydetmesi). const std::string bomlu = std::string("\xEF\xBB\xBF") + t.metin; FIKO_KONTROL(lisansZarfCoz(bomlu, anahtar) == bomlu); // Boş dosya: boş döner (çağıran LisansDurum::Bos görür). FIKO_KONTROL(lisansZarfCoz("", anahtar).empty()); }