AMQP 1 İstemcisi Delphi Güncellemesi

· Özellikler

sgcWebSockets'teki AMQP 1.0 protokol uygulaması, OASIS AMQP 1.0 spesifikasyonuna karşı kapsamlı bir incelemeden geçti. Bu makale; kritik hataları, bellek sızıntılarını, spesifikasyon uyumluluğunu, durum makinesi doğruluğunu, heartbeat işlemeyi ve iş parçacığı güvenliği iyileştirmelerini kapsayan, 8 kaynak dosyada uygulanan 30 düzeltmeyi belgeler.

İçindekiler

  1. Genel Bakış
  2. Kritik Hata Düzeltmeleri
  3. Bellek Sızıntısı Düzeltmeleri
  4. Spesifikasyon Uyumluluğu Düzeltmeleri
  5. Durum Makinesi ve Bağlantı Düzeltmeleri
  6. Heartbeat ve Boşta Zaman Aşımı Düzeltmeleri
  7. İş Parçacığı Güvenliği Düzeltmeleri
  8. Değiştirilen Dosyalar

1. Genel Bakış

AMQP 1.0 uygulamasında 6 kaynak dosya ve 2 arayüz dosyasında toplam 30 düzeltme uygulandı. Düzeltmeler aşağıdaki gibi kategorize edilmiştir:

Kategori Sayı Önem
Kritik Hata Düzeltmeleri 6 Kritik
Bellek Sızıntısı Düzeltmeleri 4 Kritik
Spesifikasyon Uyumluluğu 10 Yüksek
Durum Makinesi ve Bağlantı 5 Yüksek
Heartbeat ve Boşta Zaman Aşımı 3 Yüksek
İş Parçacığı Güvenliği 2 Yüksek

2. Kritik Hata Düzeltmeleri

Bu düzeltmeler; AMQP 1.0 iletişimi sırasında anında çalışma zamanı hatalarına, protokol bozulmasına veya yanlış davranışa neden olabilecek hataları ele alır.

2.1 İstisnada Eksik raise Anahtar Sözcüğü

Dosya: sgcAMQP1_Classes.pas
Geçersiz bir çerçeve türüyle karşılaşıldığında bir istisna nesnesi oluşturuluyordu ancak hiç oluşturulmuyordu (raise edilmiyordu). İstisna sessizce atılıyor, bozuk çerçevelerin tespit edilmeden işlenmesine izin veriyordu.

Önce (Bozuk)

TsgcAMQP1Exception.CreateFmt(S_AMQP1_INVALID_FRAME_TYPE, [vByte]);

Sonra (Düzeltilmiş)

raise TsgcAMQP1Exception.CreateFmt(S_AMQP1_INVALID_FRAME_TYPE, [vByte]);

2.2 WriteMap Map32 Eksik Veri ve Yanlış Boyut

Dosya: sgcAMQP1_Classes.pas
Map32 kodlama yolunda gerçek map verileri için WriteBytes çağrısı eksikti ve boyut alanı yanlış bir offset kullanıyordu. Map32, 4 baytlık bir sayım alanı kullanır (1 bayt kullanan Map8'in aksine), bu nedenle boyut 4 ek bayt içermelidir.

Önce (Bozuk)

else
begin
  WriteUByte(Ord(amqp1DataMap32));
  _WriteUInt32(vSize + 1);
  _WriteUInt32(oJSON.Count * 2);
  // Missing: WriteBytes(oArray.Bytes);
end;

Sonra (Düzeltilmiş)

else
begin
  WriteUByte(Ord(amqp1DataMap32));
  _WriteUInt32(vSize + 4);
  _WriteUInt32(oJSON.Count * 2);
  WriteBytes(oArray.Bytes);
end;

2.3 Ters Çevrilmiş ContainsError Mantığı

Dosya: sgcAMQP1_Frames.pas
Hem TsgcAMQP1FrameRejected hem de TsgcAMQP1DescribedListError'daki ContainsError metodu, hata olmadığında True ve hata olduğunda False döndürüyordu. Bu, gerçek hatalar serileştirilmesi gerekirken hata bilgisinin sessizce atılmasına ve null baytların yazılmasına neden oluyordu. DoWrite ve DoWriteError dalları da düzeltilen mantığa uyacak şekilde değiştirildi.

Önce (Bozuk)

function TsgcAMQP1FrameRejected.ContainsError: Boolean;
begin
  if not Assigned(FError) then
    Result := True              // Wrong: True when no error
  else
    Result := (Error.Condition = '') and (Error.Description = '') and
      (Error.Info = '');     // Wrong: True when all empty
end;

Sonra (Düzeltilmiş)

function TsgcAMQP1FrameRejected.ContainsError: Boolean;
begin
  if not Assigned(FError) then
    Result := False             // Correct: False when no error
  else
    Result := (Error.Condition <> '') or (Error.Description <> '') or
      (Error.Info <> '');    // Correct: True when any field set
end;

2.4 SASL PLAIN Null Bayt Ayırıcıları

Dosya: sgcAMQP1_Frames.pas
SASL PLAIN mekanizması, null bayt ($00) ayırıcılarıyla \0username\0password biçimini gerektirir. Uygulama, bayt dizisini sıfıra başlatmıyordu, bu nedenle ayırıcı konumları çöp veri içeriyordu. Kimlik doğrulaması, standartlara uygun herhangi bir AMQP 1.0 broker'ına karşı başarısız oluyordu.

Sonra (Düzeltilmiş)

SetLength(FInitialResponse, 2 + Length(oUser) + Length(oPassword));
FillChar(FInitialResponse[0], Length(FInitialResponse), 0);  // Zero-fill for null separators

2.5 TsgcAMQP1Message'da Eksik inherited Create

Dosya: sgcAMQP1_Message.pas
TsgcAMQP1Message'ın parametreli yapıcısı, inherited Create'i çağırmıyordu, bu da temel sınıfın hiç başlatılmadığı anlamına geliyordu. Bu, kolaylık yapıcısı kullanılırken erişim ihlallerine veya bozuk duruma neden oluyordu.

Sonra (Düzeltilmiş)

constructor TsgcAMQP1Message.Create(const aValue: string);
begin
  inherited Create;
  ApplicationData.ValueType := amqp1adtAmqpValue;
  ApplicationData.AMQPValue.Value := aValue;
end;

2.6 AmqpValue.DoRead'de Eksik Noktalı Virgül

Dosya: sgcAMQP1_Frames.pas
TsgcAMQP1FrameAmqpValue.DoRead'deki eksik bir noktalı virgül derlemeyi engelliyordu.


3. Bellek Sızıntısı Düzeltmeleri

Bu düzeltmeler; özellikle birçok mesaj alışverişi olan uzun süreli AMQP 1.0 bağlantıları sırasında belleğin zamanla birikmesine neden olabilecek nesne ömrü yönetimi sorunlarını ele alır.

Düzeltme Dosya Açıklama
3.1 sgcAMQP1_Frames.pas Source tanımlayıcısı okunurken FDefaultOutcome yeniden atamadan önce serbest bırakılmıyordu. Her yeni varsayılan sonuç alındığında önceki nesne sızdırılıyordu.
3.2 sgcAMQP1_Session.pas Yıkıcıdaki yinelenen sgcFree(FCreditConsumed) çağrısı olası bir çift serbest bırakmaya neden oluyordu. Yinelenen satır kaldırıldı.
3.3 sgcAMQP1_Session.pas Oturum yıkıcısında FOutgoingDeliveries eksikti. Teslimat izleme listesi, bir oturum yok edildiğinde hiç serbest bırakılmıyordu.
3.4 sgcAMQP1_Message.pas SetMessage ve SetMessageAndFreeOnDestroy, FFreeMessageOnDestroy etkin olduğunda önceki mesajı serbest bırakmıyordu. Tekrarlanan mesaj atamaları bellek sızdırıyordu.

Düzeltme 3.1 – FDefaultOutcome Yeniden Atamadan Önce Serbest Bırakıldı

sgcFree(FDefaultOutcome);  // Free previous instance before reassignment
if oDescriptor.Code = amqp1dcptReleased then
  FDefaultOutcome := TsgcAMQP1FrameReleased.Create
else if oDescriptor.Code = amqp1dcptAccepted then
  FDefaultOutcome := TsgcAMQP1FrameAccepted.Create
else if oDescriptor.Code = amqp1dcptRejected then
  FDefaultOutcome := TsgcAMQP1FrameRejected.Create

Düzeltme 3.4 – SetMessage Eski Mesajı Serbest Bırakır

procedure TsgcAMQP1Delivery.SetMessage(const aMessage: TsgcAMQP1Message);
begin
  if FFreeMessageOnDestroy and Assigned(F_Message) and (F_Message <> aMessage) then
    sgcFree(F_Message);
  FFreeMessageOnDestroy := False;
  F_Message := aMessage;
end;

4. Spesifikasyon Uyumluluğu Düzeltmeleri

Bu düzeltmeler; AMQP 1.0 Transport, Types ve Messaging spesifikasyonlarından sapmaları düzeltir.

4.1 Begin Çerçevesi Alan 7 Yanlış Özelliğe Okunuyordu

Dosya: sgcAMQP1_Frames.pas
begin performatifinin 7. alan dizini, Properties yerine DesiredCapabilities'e okunuyordu. Spesifikasyona göre, begin alanları şunlardır: remote-channel(0), next-outgoing-id(1), incoming-window(2), outgoing-window(3), handle-max(4), offered-capabilities(5), desired-capabilities(6), properties(7).

4.2 DoWrite'da Source ve Target Eksik Alanlar

Dosya: sgcAMQP1_Frames.pas
Source tanımlayıcısının DoWrite metodu, default-outcome, outcomes ve capabilities alanlarını serileştirmiyordu. Target tanımlayıcısında capabilities eksikti. Bu, broker'ın müzakere edilen değerler yerine varsayılanları kullanmasına neden oluyor ve olası olarak yanlış teslimat durumu işlemeye yol açıyordu.

4.3 AmqpSequence Yanlış Özelliğe Okunuyordu

Dosya: sgcAMQP1_Message.pas
Mesaj gövdesi okunurken, amqp-sequence verileri ApplicationData.AMQPSequence yerine ApplicationData.AMQPValue'a okunuyordu. Bu, amqp-sequence kodlamasını kullanan herhangi bir mesajın gövdesini bozuyordu.

4.4 TransactionalState Outcome Yazılmıyordu

Dosya: sgcAMQP1_Frames.pas
transactional-state teslimat durumu, bir işlem içinde teslimatları sonlandırırken gerekli olan outcome alanını serileştirmiyordu.

4.5 Disposition Last Alanı Sıfırı Ayarlanmamıştan Ayırt Edemiyor

Dosyalar: sgcAMQP1_Frames.pas, sgcAMQP1_Frames.intf, sgcAMQP1_Session.pas
disposition performatifinin isteğe bağlı bir last alanı (delivery-id) vardır. Bu bir Cardinal olduğundan, 0 değeri geçerlidir ve “ayarlanmadı” için bir nöbetçi olarak kullanılamaz. Alanın açıkça ayarlanıp ayarlanmadığını düzgün şekilde izlemek için yeni bir FLastAssigned boolean bayrağı ve SetLast setter'ı eklendi.

procedure TsgcAMQP1FrameDisposition.SetLast(const Value: Cardinal);
begin
  FLast := Value;
  FLastAssigned := True;
end;

4.6 AmqpSequence Eksik Value Özelliği ve Okuma/Yazma

Dosyalar: sgcAMQP1_Frames.pas, sgcAMQP1_Frames.intf
TsgcAMQP1FrameAmqpSequence sınıfının Value özelliği yoktu ve boş DoRead/DoWrite metotları vardı. amqp-sequence gövde türü tamamen işlevsizdi.

4.7 Error Info Alanı Map Yerine String Olarak Okunuyordu

Dosya: sgcAMQP1_Frames.pas
AMQP 1.0 spesifikasyonu, error türünün info alanını bir map olarak tanımlar. Bu alan, ReadMap yerine ReadString ile okunuyordu ve broker'lar yapılandırılmış hata bilgisi gönderdiğinde ayrıştırma hatalarına neden oluyordu.

4.8 Capabilities ve Locales Symbol Yerine String Olarak Yazılıyordu

Dosya: sgcAMQP1_Frames.pas
AMQP 1.0 spesifikasyonu; offered-capabilities, desired-capabilities, outgoing-locales ve incoming-locales'i symbol dizileri olarak tanımlar. Bunlar open, begin ve attach performatiflerinde WriteSymbol yerine WriteString ile yazılıyordu. Standartlara uygun bir broker, bu çerçeveleri yanlış alan türlerine sahip olarak reddederdi.

4.9 DefaultOutcome İşleyicisinde Eksik Accepted Tanımlayıcısı

Dosya: sgcAMQP1_Frames.pas
Source tanımlayıcısının default-outcome okuyucusu yalnızca released ve rejected'ı işliyordu. En yaygın sonuç olan accepted işlenmiyordu. Bir broker varsayılan sonuç olarak accepted gönderdiğinde, sessizce yok sayılıyordu.


5. Durum Makinesi ve Bağlantı Düzeltmeleri

Bu düzeltmeler, AMQP 1.0 bağlantı durum makinesini ve çerçeve işleme mantığını ele alır.

Düzeltme Dosya Açıklama
5.1 sgcAMQP1.pas amqp1csOpenReceived durum geçişinde, diğer tüm durumların sahip olduğu else DoRaiseInvalidState eksikti. Geçersiz geçişler, bir hata oluşturmak yerine sessizce yok sayılıyordu.
5.2 sgcAMQP1.pas Çerçeve boyutu doğrulama hata mesajı RemoteMaxFrameSize'ı görüntülüyordu ancak kontrol edilen yerel limit olduğundan LocalMaxFrameSize'ı göstermeliydi.
5.3 sgcAMQP1.pas FLastTimeRead, Now yerine 0'a (Delphi epoch: 1899-12-30) başlatılıyordu. Bu, başlangıçta anında yanlış boşta zaman aşımı tespitine neden oluyordu.
5.4 sgcAMQP1.pas Read'in TBytes aşırı yüklemesi, TMemoryStream aşırı yüklemesinin aksine FLastTimeRead := Now'ı güncellemiyordu. Bu, tutarsız heartbeat izlemeye neden oluyordu.
5.5 sgcAMQP1.pas Header alınan durum geçişi, her zaman tetiklenmesi gerekirken koşulluydu. Durum makinesi, AMQP 1.0 spesifikasyonuna göre her geçerli header alışverişinde geçiş yapmalıdır.

Düzeltme 5.1 – OpenReceived Eksik Hata Dalı

amqp1csOpenReceived:
  begin
    if aState = amqp1csOpenSent then
      FConnectionState := amqp1csOpened
    else
      DoRaiseInvalidState;  // Added: was missing
  end;

6. Heartbeat ve Boşta Zaman Aşımı Düzeltmeleri

AMQP 1.0 spesifikasyonu, bir eşin open performatifinde bir idle-timeout duyurduğunda, diğer eşin duyurulan aralığın yarısında heartbeat çerçeveleri göndermesini gerektirir. Bu düzeltmeler, heartbeat mekanizmasının gerçekten çalışmasını sağlar.

Düzeltme Dosya Açıklama
6.1 sgcAMQP1_Client.pas HeartBeat hiç etkinleştirilmiyordu. Boşta zaman aşımı kontrolünün her iki dalı da HeartBeat.Enabled := False ayarlıyordu. IdleTimeout > 0 olduğunda True olarak değiştirildi.
6.2 sgcAMQP1_Client.pas Disconnect, heartbeat'i devre dışı bırakmıyor veya FConnected := False'i yeterince erken ayarlamıyordu. Yıkım sırasında heartbeat'in tetiklenmesini önlemek için yeniden sıralandı.
6.3 sgcAMQP1.pas TBytes Read aşırı yüklemesinde FLastTimeRead güncellenmiyor (Durum Makinesi bölümünde de listelenmiştir).

Düzeltme 6.1 – HeartBeat Etkinleştirildi

if oOpen.IdleTimeout > 0 then
begin
  HeartBeat.Interval := Trunc(oOpen.IdleTimeout / 2);
  HeartBeat.Enabled := True;   // Was: False (heartbeat never started)
end
else
  HeartBeat.Enabled := False;

Düzeltme 6.2 – Disconnect Yeniden Sıralandı

procedure TsgcAMQP1_Client.Disconnect;
begin
  FConnected := False;           // Moved first: prevents heartbeat race
  DoStopIdleTimeout;
  HeartBeat.Enabled := False;    // Added: stop heartbeat during teardown
  Clear;
  DoConnectionState(amqp1csEnd);
end;

7. İş Parçacığı Güvenliği Düzeltmeleri

Bu düzeltmeler, paylaşılan veri yapılarına eşzamanlı erişimdeki yarış koşullarını ele alır.

7.1 TsgcAMQP1Deliveries.First() Sınır Kontrolü ve Kilitleme

Dosya: sgcAMQP1_Message.pas
First() metodu, listenin boş olup olmadığını kontrol etmeden ve iş parçacığı güvenli kilidi almadan Items[0]'a erişiyordu. Çok iş parçacıklı bir ortamda, başka bir iş parçacığı sayım kontrolü ile erişim arasında tüm öğeleri kaldırabilir ve bir index-out-of-bounds istisnasına neden olabilirdi.

Sonra (Düzeltilmiş)

function TsgcAMQP1Deliveries.First: TsgcAMQP1Delivery;
var
  oList: TList;
begin
  result := nil;
  oList := LockList;
  Try
    if oList.Count > 0 then
      result := TsgcAMQP1Delivery(oList[0]);
  Finally
    UnLockList;
  End;
end;

7.2 SetMessage Güvenli Nesne Değiştirme

Dosya: sgcAMQP1_Message.pas
SetMessage metodu artık serbest bırakmadan önce yeni mesajın geçerli olandan farklı olduğunu kontrol eder ve aynı mesaj nesnesi atanırken use-after-free durumunu önler.


8. Değiştirilen Dosyalar

Dosya Düzeltmeler Kategoriler
Source\sgcAMQP1_Classes.pas 2 Kritik hatalar
Source\sgcAMQP1_Frames.pas 16 Kritik hatalar, bellek sızıntıları, spesifikasyon uyumluluğu
Interfaces\sgcAMQP1_Frames.intf 2 Spesifikasyon uyumluluğu (Disposition LastAssigned, AmqpSequence Value)
Source\sgcAMQP1_Message.pas 4 Kritik hatalar, bellek sızıntıları, iş parçacığı güvenliği
Source\sgcAMQP1_Session.pas 3 Bellek sızıntıları, spesifikasyon uyumluluğu
Source\sgcAMQP1.pas 5 Durum makinesi, bağlantı, heartbeat
Source\sgcAMQP1_Client.pas 3 Heartbeat, bağlantı kesme güvenliği

8 dosyada Toplam: 30 düzeltme; AMQP 1.0 uygulamasının OASIS spesifikasyonuna karşı protokol doğruluğunu, bellek güvenliğini ve güvenilirliğini iyileştirir.