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
- Genel Bakış
- Kritik Hata Düzeltmeleri
- Bellek Sızıntısı Düzeltmeleri
- Spesifikasyon Uyumluluğu Düzeltmeleri
- Durum Makinesi ve Bağlantı Düzeltmeleri
- Heartbeat ve Boşta Zaman Aşımı Düzeltmeleri
- İş Parçacığı Güvenliği Düzeltmeleri
- 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.
