#include "fiko/istakip/kanal.hpp" #include namespace fiko::istakip { using namespace istakipproto; using orkestra::NodeAdres; // ── Kral ───────────────────────────────────────────────────────────────────── IsKralKanal::IsKralKanal(IsMerkez& merkez, orkestra::TasimaArayuz& tasima, size_t parcaBoyu) : merkez_(merkez), tasima_(tasima), parcaBoyu_(parcaBoyu ? parcaBoyu : 1) { tasima_.aliciAyarla([this](const NodeAdres& kaynak, const SurecCerceve& c) { cerceveAl(kaynak, c); }); } void IsKralKanal::istemciDustu(const NodeAdres& adres) { istemciler_.erase(std::remove(istemciler_.begin(), istemciler_.end(), adres), istemciler_.end()); kanalKimlik_.erase(adres.nodeId); kimlikDurum_.erase(adres.nodeId); } bool IsKralKanal::kimlikGecti(const NodeAdres& kaynak, uint64_t istemciNo, const SurecCerceve& bekleyen) { if (!kimlikZorunlu_) return true; KimlikDurum& kd = kimlikDurum_[kaynak.nodeId]; if (kd.dogrulandi) return true; SurecCerceve cevap; cevap.korelasyon = bekleyen.korelasyon; const std::array* anahtar = anahtarBul_ ? anahtarBul_(istemciNo) : nullptr; if (!anahtar) { // eşleşmemiş makine: önce Orchestrator'dan eşleştirilmeli cevap.tip = SurecMesajTip::Onay; cevap.govde = fiko::surecproto::onaySerialize(false, "eslesme"); tasima_.gonder(kaynak, cevap); return false; } if (rastgele_) rastgele_(kd.nonce.data(), kd.nonce.size()); kd.bekleyen = bekleyen; // kanıt doğrulanınca kaldığı yerden işlenir kd.bekleyenVar = true; cevap.tip = SurecMesajTip::IsKimlikSinama; fiko::surecproto::yazU64(cevap.govde, kendiNo_); cevap.govde.append((const char*)kd.nonce.data(), kd.nonce.size()); tasima_.gonder(kaynak, cevap); return false; } std::vector IsKralKanal::istemciKimlikleri() const { std::vector sonuc; for (const auto& adres : istemciler_) { const auto it = kanalKimlik_.find(adres.nodeId); if (it != kanalKimlik_.end()) sonuc.push_back(it->second); } return sonuc; } void IsKralKanal::cerceveAl(const NodeAdres& kaynak, const SurecCerceve& c) { switch (c.tip) { case SurecMesajTip::IsSnapshotIste: { uint64_t sonSurum = 0, istemciNo = 0; if (!snapshotIsteCoz(c.govde, sonSurum, istemciNo)) return; if (!kimlikGecti(kaynak, istemciNo, c)) return; // zorunlu kipte önce kanıt if (std::find(istemciler_.begin(), istemciler_.end(), kaynak) == istemciler_.end()) istemciler_.push_back(kaynak); kanalKimlik_[kaynak.nodeId] = istemciNo; // kanal ↔ kalıcı kimlik bağı // Parçalı gönderim: dev dökümler dilimlenir; her parçanın yeniSurum'u kendi // son kaydıdır (kopmada istemci kaldığı sürümden devam eder), son parça // devamVar=0 + merkezin gerçek sürümü. const auto kayitlar = merkez_.surumdenBeri(sonSurum); const uint64_t islenen = merkez_.islenenSira(istemciNo); size_t bas = 0; do { const size_t adet = std::min(parcaBoyu_, kayitlar.size() - bas); const bool sonParca = bas + adet >= kayitlar.size(); std::vector parca(kayitlar.begin() + bas, kayitlar.begin() + bas + adet); SurecCerceve cevap; cevap.tip = SurecMesajTip::IsSnapshot; cevap.korelasyon = c.korelasyon; cevap.govde = snapshotSerialize( parca, sonParca ? merkez_.sonSurum() : parca.back().surum, islenen, !sonParca); if (!tasima_.gonder(kaynak, cevap)) break; // kopma: istemci tekrar ister bas += adet; } while (bas < kayitlar.size()); break; } case SurecMesajTip::IsYaz: { uint64_t istemciNo = 0, sira = 0, beklenenSurum = 0; IsKayit taslak; if (!yazCoz(c.govde, istemciNo, sira, beklenenSurum, taslak)) return; if (!kimlikGecti(kaynak, istemciNo, c)) return; // zorunlu kipte önce kanıt SurecCerceve cevap; cevap.korelasyon = sira; // Kimlik tutarlılığı: IsYaz'daki no, kanalın snapshot'ta bağladığı kimlikle // eşleşmeli (başka istemcinin kimliğine bürünüp sıra haritasını bozamaz). const auto kim = kanalKimlik_.find(kaynak.nodeId); const bool kimlikUyumsuz = kim != kanalKimlik_.end() && kim->second != istemciNo; if (kimlikUyumsuz || (yazIzin_ && !yazIzin_(kaynak, istemciNo, taslak))) { ++redYetki_; cevap.tip = SurecMesajTip::Onay; cevap.govde = fiko::surecproto::onaySerialize(false, "yetki"); tasima_.gonder(kaynak, cevap); return; } const auto sonuc = merkez_.yaz(istemciNo, sira, taslak, beklenenSurum); if (sonuc.cakisma) { ++redCakisma_; cevap.tip = SurecMesajTip::Onay; cevap.govde = fiko::surecproto::onaySerialize(false, "cakisma"); tasima_.gonder(kaynak, cevap); return; } if (sonuc.depoHata) { cevap.tip = SurecMesajTip::Onay; cevap.govde = fiko::surecproto::onaySerialize(false, "depo"); tasima_.gonder(kaynak, cevap); return; } if (!sonuc.uygulandi) { // tekrar istek: bekleyeni düşsün diye onayla cevap.tip = SurecMesajTip::Onay; cevap.govde = fiko::surecproto::onaySerialize(true, "tekrar"); tasima_.gonder(kaynak, cevap); return; } ++uygulananYazma_; deltaYayinla(sonuc.kayit, istemciNo, sira); break; } case SurecMesajTip::IsDosyaIste: { if (kimlikZorunlu_) { // dosya isteğinde kimlik alanı yok → kanal doğrulanmış olmalı const auto it = kimlikDurum_.find(kaynak.nodeId); if (it == kimlikDurum_.end() || !it->second.dogrulandi) { SurecCerceve red; red.tip = SurecMesajTip::Onay; red.korelasyon = c.korelasyon; red.govde = fiko::surecproto::onaySerialize(false, "kimlik"); tasima_.gonder(kaynak, red); return; } } std::string yol; const char* p = c.govde.data(); const char* son = p + c.govde.size(); if (!fiko::surecproto::okuStr(p, son, yol)) return; std::string icerik; // Güvenlik: yalnız iş kayıtlarında geçen yollar (dosyaIzin) okunup verilir. if (!dosyaIzin_ || !dosyaOku_ || !dosyaIzin_(yol) || !dosyaOku_(yol, icerik)) { SurecCerceve cevap; cevap.tip = SurecMesajTip::Onay; cevap.korelasyon = c.korelasyon; cevap.govde = fiko::surecproto::onaySerialize(false, "dosya"); tasima_.gonder(kaynak, cevap); return; } for (const auto& parca : orkestra::dosyayiParcala(++sonTransferId_, icerik, dosyaParcaBoy_)) { SurecCerceve pc; pc.tip = SurecMesajTip::DosyaParca; pc.korelasyon = c.korelasyon; pc.govde = orkestra::dosyaproto::parcaSerialize(parca); if (!tasima_.gonder(kaynak, pc)) break; // kopma: istemci yeniden ister } break; } case SurecMesajTip::IsKimlikKanit: { if (c.govde.size() < 40) return; // [u64 istemciNo][32B kanit] const uint64_t istemciNo = fiko::surecproto::okuU64(c.govde.data()); const auto it = kimlikDurum_.find(kaynak.nodeId); if (it == kimlikDurum_.end() || it->second.dogrulandi) return; // sınama yoktu const std::array* anahtar = anahtarBul_ ? anahtarBul_(istemciNo) : nullptr; SurecCerceve cevap; if (!anahtar) return; const auto beklenen = kimlikKanitTuret(*anahtar, it->second.nonce, istemciNo); if (!std::equal(beklenen.begin(), beklenen.end(), (const uint8_t*)c.govde.data() + 8)) { cevap.tip = SurecMesajTip::Onay; cevap.govde = fiko::surecproto::onaySerialize(false, "kimlik"); tasima_.gonder(kaynak, cevap); return; } it->second.dogrulandi = true; if (it->second.bekleyenVar) { // sınamaya takılan ilk istek kaldığı yerden işlenir SurecCerceve bekleyen = std::move(it->second.bekleyen); it->second.bekleyenVar = false; cerceveAl(kaynak, bekleyen); } break; } default: break; // bilinmeyen tip → no-op (ileri-uyum) } } void IsKralKanal::deltaYayinla(const IsKayit& kayit, uint64_t yazarNo, uint64_t yazarSira) { SurecCerceve delta; delta.tip = SurecMesajTip::IsDelta; delta.korelasyon = yazarSira; delta.govde = deltaSerialize(kayit, yazarNo, yazarSira); for (const auto& adres : istemciler_) tasima_.gonder(adres, delta); } // ── İstemci ────────────────────────────────────────────────────────────────── IsIstemciKanal::IsIstemciKanal(IsIstemci& istemci, orkestra::TasimaArayuz& tasima, NodeAdres kral) : istemci_(istemci), tasima_(tasima), kral_(kral) { tasima_.aliciAyarla([this](const NodeAdres& kaynak, const SurecCerceve& c) { cerceveAl(kaynak, c); }); } bool IsIstemciKanal::dosyaIste(const std::string& yol) { if (!bagli_ || yol.empty()) return false; dosyaIstekNo_ = ++dosyaSayac_; // öncekini iptal: eski korelasyonun parçaları atılır dosyaIstekYol_ = yol; dosyaBirlestirici_ = {}; SurecCerceve c; c.tip = SurecMesajTip::IsDosyaIste; c.korelasyon = dosyaIstekNo_; fiko::surecproto::yazStr(c.govde, yol); if (!tasima_.gonder(kral_, c)) { bagli_ = false; dosyaIstekNo_ = 0; return false; } return true; } void IsIstemciKanal::baglan() { SurecCerceve c; c.tip = SurecMesajTip::IsSnapshotIste; c.govde = snapshotIsteSerialize(istemci_.sonSurum(), istemci_.istemciNo()); if (!tasima_.gonder(kral_, c)) bagli_ = false; } uint64_t IsIstemciKanal::yaz(const IsKayit& taslak, uint64_t beklenenSurum) { const uint64_t sira = istemci_.yazKuyrukla(taslak, beklenenSurum); if (bagli_) bekleyenleriGonder(); return sira; } void IsIstemciKanal::bekleyenleriGonder() { for (const auto& b : istemci_.bekleyenler()) { SurecCerceve c; c.tip = SurecMesajTip::IsYaz; c.korelasyon = b.sira; c.govde = yazSerialize(istemci_.istemciNo(), b.sira, b.beklenenSurum, b.taslak); if (!tasima_.gonder(kral_, c)) { bagli_ = false; return; } // kuyrukta kalır } } void IsIstemciKanal::cerceveAl(const NodeAdres& /*kaynak*/, const SurecCerceve& c) { switch (c.tip) { case SurecMesajTip::IsSnapshot: { std::vector kayitlar; uint64_t yeniSurum = 0, islenenSira = 0; bool devamVar = false; if (!snapshotCoz(c.govde, kayitlar, yeniSurum, islenenSira, devamVar)) return; istemci_.snapshotUygula(kayitlar, yeniSurum, islenenSira); if (devamVar) break; // parça: senkron ancak son parçada tamamlanır bagli_ = true; bekleyenleriGonder(); // kopukken biriken yazmalar akar break; } case SurecMesajTip::IsDelta: { IsKayit k; uint64_t yazarNo = 0, yazarSira = 0; if (!deltaCoz(c.govde, k, yazarNo, yazarSira)) return; istemci_.deltaUygula(k, yazarNo, yazarSira); break; } case SurecMesajTip::IsKimlikSinama: { if (c.govde.size() < 24) break; // [u64 kralNo][16B nonce] const uint64_t kralNo = fiko::surecproto::okuU64(c.govde.data()); std::array nonce{}; std::copy_n((const uint8_t*)c.govde.data() + 8, nonce.size(), nonce.begin()); const std::array* anahtar = anahtarBul_ ? anahtarBul_(kralNo) : nullptr; if (!anahtar) { // kralla eşleşmemişiz — kullanıcı Orchestrator'dan eşleştirmeli if (yazHata_) yazHata_(0, "eslesme"); break; } const auto kanit = kimlikKanitTuret(*anahtar, nonce, istemci_.istemciNo()); SurecCerceve cevap; cevap.tip = SurecMesajTip::IsKimlikKanit; fiko::surecproto::yazU64(cevap.govde, istemci_.istemciNo()); cevap.govde.append((const char*)kanit.data(), kanit.size()); tasima_.gonder(kral_, cevap); break; } case SurecMesajTip::DosyaParca: { if (!dosyaIstekNo_ || c.korelasyon != dosyaIstekNo_) break; // bayat/yabancı parça orkestra::DosyaParcaGovde parca; if (!orkestra::dosyaproto::parcaCoz(c.govde, parca)) break; const auto sonuc = dosyaBirlestirici_.ekle(parca); if (sonuc == orkestra::ParcaBirlestirici::Sonuc::Tamam) { if (dosyaSonuc_) dosyaSonuc_(true, dosyaIstekYol_, dosyaBirlestirici_.icerik()); dosyaIstekNo_ = 0; dosyaBirlestirici_ = {}; } else if (sonuc == orkestra::ParcaBirlestirici::Sonuc::HashHatasi) { if (dosyaSonuc_) dosyaSonuc_(false, dosyaIstekYol_, {}); dosyaIstekNo_ = 0; dosyaBirlestirici_ = {}; } break; } case SurecMesajTip::Onay: { bool basarili = false; std::string sonuc; if (!fiko::surecproto::onayCoz(c.govde, basarili, sonuc)) return; if (!basarili && sonuc == "dosya") { // dosya isteği reddi (kayıtlı değil/okunamadı) if (dosyaIstekNo_ && c.korelasyon == dosyaIstekNo_) { if (dosyaSonuc_) dosyaSonuc_(false, dosyaIstekYol_, {}); dosyaIstekNo_ = 0; dosyaBirlestirici_ = {}; } break; } if (basarili) { istemci_.onayUygula(c.korelasyon); // "tekrar" — bekleyen düşer } else if (sonuc == "yetki" || sonuc == "cakisma") { // Kalıcı red: sonsuza dek yeniden denemek anlamsız — bekleyen düşer, // kullanıcıya bildirilir (çakışmada taze kaydı görüp yeniden düzenler). istemci_.bekleyenSil(c.korelasyon); if (yazHata_) yazHata_(c.korelasyon, sonuc); } else if (yazHata_) { yazHata_(c.korelasyon, sonuc); // "depo": bekleyen kalır, sonra denenir } break; } default: break; } } } // namespace fiko::istakip