Release 2026.9.0: sgcWebSockets, sgcSign, sgcIndy and sgcOpenAPI

· Releases
Release 2026.9.0 for the eSeGeCe component libraries for Delphi, C++ Builder and .NET

2026.9.0 is out for every product at once: sgcWebSockets, sgcWebSockets .NET, sgcSign, sgcIndy and sgcOpenAPI. It is the largest release of the year. Two entirely new packages join sgcWebSockets, the server engines were reworked around a problem that had been losing requests for a long time, and the signature library learned to verify a signature against something other than itself.

This post walks through what matters in each product, with the Delphi code where it helps. Everything is in the changelog, and the parts that change existing behaviour are collected near the end so you can read them before you upgrade.

The whole release in under four minutes. Also on YouTube.

ProductNewFixesBreaking
sgcWebSockets255622
sgcSign257021
sgcOpenAPI9255
sgcWebSockets .NET52410
sgcIndy050

sgcWebSockets 2026.9.0

sgcWebRTC, a Native WebRTC Media Engine

The new sgcWebRTC package turns TsgcRTCPeerConnection into a complete WebRTC endpoint. Audio and video calls, screen sharing and SCTP data channels, all of it in Pascal, with no browser, no WebView control and no external media library. It runs on Windows, Linux, macOS, iOS and Android, and the peer at the other end can be a browser, because the SDP it produces and consumes is the real thing.

The offer and answer exchange follows the W3C shape, so CreateOffer, CreateAnswer, SetLocalDescription, SetRemoteDescription and AddIceCandidate do what their names say. You carry the SDP over whatever signalling channel you already have, which can be a WebSocket server built with the same library.

uses
  sgcP2P;

var
  oRTC: TsgcRTCPeerConnection;
begin
  oRTC := TsgcRTCPeerConnection.Create(nil);
  oRTC.RTCOptions.ICEServers.AddURL('stun:stun.l.google.com:19302');
  oRTC.OnLocalDescription      := OnLocalDescriptionHandler;
  oRTC.OnICECandidate          := OnICECandidateHandler;
  oRTC.OnConnectionStateChange := OnConnectionStateChangeHandler;
  oRTC.OnDataChannel           := OnDataChannelHandler;

  oRTC.CreateDataChannel('chat');   // forces RTCOptions.DTLS on
  oRTC.CreateOffer;                 // gathers ICE candidates, builds the SDP offer
end;

procedure TForm1.OnLocalDescriptionHandler(Sender: TObject;
  const aType, aSDP: string);
begin
  // send aType + aSDP to the remote peer over your own signalling channel
end;

Media works the same way. AddTrack attaches an Opus or G.711 audio track, or a VP8, H.264 or Motion JPEG video track, SendPCM and SendVideoFrame push captured media in, and OnAudio and OnVideoFrame hand you the decoded remote track.

The old browser-era fallbacks went the other way. Support for the AppRTC protocol and the RTCMultiConnection API has been removed, and so has the Flash fallback, which reached end of life in 2020. See the upgrade notes at the end.

sgcCrypto, Cryptography Without OpenSSL

The second new package is sgcCrypto, a pure Pascal implementation of the primitives most applications actually reach for. AES and ChaCha20/XChaCha20-Poly1305 authenticated encryption, SHA-2 and SHA-3, Argon2, scrypt and HKDF for passwords and key derivation, Ed25519, Ed448, X25519, X448 and secp256k1, RSA key generation, X.509 certificate and CSR generation, and the post-quantum ML-KEM, ML-DSA and SLH-DSA algorithms.

Each primitive is a plain function. There is no context object to create, configure and free, and no DLL to ship next to the executable.

uses
  sgcCrypto_Random, sgcCrypto_AES, sgcCrypto_Keccak, sgcCrypto_Ed25519,
  sgcCrypto_MLKEM;

var
  vKey, vIV, vPlain, vAAD, vTag, vCipher: TBytes;
  vDigest, vSeed, vSignature, vMessage: TBytes;
  vPublicKey, vPrivateKey, vSharedSecret, vCiphertext: TBytes;
begin
  // AES-256-GCM: authenticated encryption in one call
  vKey    := sgcRandomBytes(32);
  vIV     := sgcRandomBytes(12);
  vPlain  := TEncoding.UTF8.GetBytes('confidential payload');
  vCipher := sgcAES_GCM_Encrypt(vKey, vIV, vPlain, vAAD, vTag);

  // SHA-3-256, one call, no context object to manage
  vDigest := sgcSHA3_256(vPlain);

  // Ed25519: sign, then verify
  vSeed      := sgcRandomBytes(32);
  vMessage   := TEncoding.UTF8.GetBytes('sign me');
  vSignature := sgcEd25519_Sign(vSeed, vMessage);

  // ML-KEM-768: post-quantum key encapsulation (FIPS 203)
  sgcMLKEM_GenerateKeyPair(mlkem768, vPublicKey, vPrivateKey);
  sgcMLKEM_Encapsulate(mlkem768, vPublicKey, vCiphertext, vSharedSecret);
end;

Five Complete sgcHTML Applications

sgcHTML gains four components and five demos. The components are CameraScanner, a live camera panel that reads barcodes and QR codes with the scanner built into the browser and always offers a manual entry fallback, plus NumPad, CommandPalette and EmptyState.

The five demos are not snippets. Each one is a whole application with sign in, a database and printable reports, and they live under Demos\60.HTML\01.RunTime: warehouse management, point of sale, a reporting portal, a multi-tenant SaaS control plane and field service dispatch.

There is also a new help topic, Runtime vs Design-Time, with demos showing how to build the same page by dropping components on a VCL form instead of composing it in code.

The Real Client Address Behind a Reverse Proxy

A server behind nginx, Apache or a cloud load balancer sees the address of the proxy on every connection, which means the blacklist, the whitelist, GeoIP and your own rules were all looking at the wrong client. The new ForwardedHeaders settings on TsgcWebSocketFirewall recover the address the proxy forwarded.

The design is deliberately suspicious. The address in X-Forwarded-For or X-Real-IP is only believed when the connection itself arrives from an address listed in TrustedProxies, so a client cannot invent one, and the chain is read from the right, past the number of proxies given in TrustedHops, because the leftmost entry is the one the client supplied. Resolution runs on every request, since a proxy reuses one connection for requests from different callers.

uses
  sgcWebSocket;

begin
  oFirewall.ForwardedHeaders.Enabled := True;
  oFirewall.ForwardedHeaders.TrustedProxies.Add('10.0.0.0/8');
  oFirewall.ForwardedHeaders.TrustedProxies.Add('::1');
  oFirewall.ForwardedHeaders.TrustedHops := 1;

  oServer.Firewall := oFirewall;
end;

procedure TForm1.OnServerConnectHandler(Connection: TsgcWSConnection);
begin
  // Connection.IP is now the end client
  // Connection.PeerIP is still the proxy that opened the socket
end;

Once resolved, that address is what every blacklist entry, every custom rule, every event and your own handler code see. The settings behave the same way on the http.sys server as on the Indy one, and the same feature arrives in the sgcSign Server as server.firewall.forwarded_headers.

Backpressure You Can Measure

Writing to a slow client faster than it reads grows the outgoing queue until something gives. There was no way to see that happening from inside the application, so relays ended up doing a stop and wait exchange over the wire just to stay safe.

Two additions replace that. PendingCount on TsgcWSConnection reports how many messages are still queued for that connection across the three priority levels, and reading it allocates nothing, so it can be polled. OnQueueDrained fires when a connection's queue goes from holding messages to being empty, on the connection thread, right after the drain and before the next read, so a credit can be handed out with no delay at all.

procedure TForm1.OnQueueDrainedHandler(Connection: TsgcWSConnection);
begin
  // raised on the connection thread: do not touch the user interface here
  SendNextBatch(Connection);
end;

procedure TForm1.SendIfRoom(Connection: TsgcWSConnection; const aText: string);
begin
  if Connection.PendingCount < 100 then
    Connection.WriteData(aText);
end;

Both cross the DLL boundary, so the .NET wrapper and any other host that consumes sgcWebSockets.dll get them too. The new exports are appended, so the existing export order is unchanged.

The EPOLL and IOCP Engines

The high performance engines had a class of defect with one cause: they assumed a request arrives in a single socket read. That is true on a loopback test and false at any MTU boundary, over a VPN, or whenever a large body is split across packets.

A request that did not all arrive was dropped with the connection closed and no reply, and a TLS record split across two TCP segments closed the connection outright. Both are fixed, and the new PartialRequestTimeout option bounds how long the server waits for the rest, on a separate thread pool so a slow client cannot hold up the others. On the EPOLL engine, server connections were also never released once the client closed, so OnDisconnect never fired and every broadcast kept writing into a dead connection.

This is also why WorkOpThreads changed meaning. It no longer pins a connection to one thread, because a connection waiting for the rest of a request held up every other connection on that thread. It now sets the smallest number of workers kept ready, and the pool grows from there.

QUIC and HTTP/3

The QUIC and HTTP/3 client and server now speak IPv6. An address carrying a colon is treated as IPv6, a URL can carry it in brackets, and a host name is resolved over both families, where before only IPv4 was tried. The HTTP/3 listener with no host set serves both families through a single socket, and a new Host property on HTTP3Options pins it to one interface when you need that.

The bigger change is that HTTP/3 requests now follow the same path as HTTP/1.1 requests. The OpenAPI, MCP and REST API servers, request forwarding, CORS, multi-tenancy and metrics never answered over HTTP/3 at all, because that transport had a path of its own. They all work now.

Certificate verification on HTTP/3 is the item to read twice. TLSOptions.VerifyCertificate was True by default on TsgcHTTP3Client, the check was performed, and the result was then thrown away, so every HTTP/3 client accepted any certificate from any authority issued to any host. The check is applied now, which means an HTTP/3 client talking to a self signed endpoint stops connecting until the trust store or the certificate is put right.

The Exchange API Clients

The ready made exchange clients had a reconnect problem that read as a much stranger problem. Private subscriptions, the order, balance and fill feeds, were never replayed after a reconnect on Bitstamp, Coinbase, Deribit, Huobi, Kraken spot and futures, Kucoin, MEXC and ThreeCommas. The component reported the reconnect as successful while those feeds were silently gone. On BitMEX, Bitfinex, Crypto.com, Deribit and Kraken futures the replay ran before authentication, which cannot work either. Frames carrying a short lived credential are now rebuilt with a fresh one at replay time instead of being resent.

Money was being rounded before it was signed. A fixed eight decimal mask turned a size such as 0.000000004 into zero, and values could go out in scientific notation or with a comma separator depending on the system locale. Values are now written losslessly, in plain decimal, with a dot, on Binance, Bybit, Cex, Cryptohopper, Kucoin, MEXC and ThreeCommas.

New in the same area: a Kraken WebSocket v2 component alongside the v1 one, a Huobi client that opens a second connection internally so one instance serves public and private data at once, UseServerTimeOffset on Binance for a host whose clock has drifted, and two throttle options, PaceBatch and AsyncResubscribe, that keep a large subscription from tripping the exchange rate limit and move the reconnect replay onto a background worker.

sgcWebSockets .NET 2026.9.0

The .NET library tracks the Delphi one. OnQueueDrained and PendingCount arrive with the same meaning, along with Throttle.AsyncResubscribe, AllowUnsignedWebhooks on the Cryptohopper client, and a new OnBinanceUserStreamSubscribed event so user data stream readiness no longer has to be discovered by polling a property.

Every exchange reconnect and signing fix listed above applies here too, and so do the removals. The AppRTC protocol, the RTCMultiConnection API and the Flash fallback classes are gone, which renumbers TwsTransport. Code that persisted or transmitted the numeric transport value needs revisiting.

Two leaks worth naming: a WebSocket API component freed while its client was still connected never removed itself from the list the message handler walks, and two heartbeat timer threads leaked for every component destroyed, which is why a clean shutdown occasionally faulted.

sgcSign 2026.9.0

Most of this release came out of customer requests, and it lands in three areas: knowing which certificate you are about to sign with, building a signature a validator will still accept in ten years, and verifying a signature against something other than itself.

Certificate Lists You Can Choose From

Enumerating certificates used to hand back display names, which is enough to fill a combo box and not enough to make a decision. Two cards from the same authority, issued to the same person, look identical in that list. The enumeration now carries the SHA-1 thumbprint, the fiscal identifier, the serial number, the issuer and the validity dates, the same way for the Windows certificate store, a PKCS#11 token and a PFX file.

uses
  sgcSign_KeyProvider_WinCertStore, sgcSign_X509, sgcSign_Types;

var
  oProvider: TsgcWindowsCertStoreProvider;
  oList: TsgcX509CertificateList;
  i: Integer;
begin
  oProvider := TsgcWindowsCertStoreProvider.Create(nil);
  Try
    // only certificates that are still valid and hold a private key
    oList := oProvider.EnumerateCertificateList([cfNotExpired, cfPrivateKey]);
    Try
      for i := 0 to oList.Count - 1 do
        Memo1.Lines.Add(Format('%s | %s | %s .. %s | %s',
          [oList[i].Subject, oList[i].SerialNumber,
           DateToStr(oList[i].NotBefore), DateToStr(oList[i].NotAfter),
           oList[i].Thumbprint]));

      // and sign with exactly the one that was chosen
      oProvider.SelectCertificateByThumbprint(oList[0].Thumbprint);
    Finally
      oList.Free;
    End;
  Finally
    oProvider.Free;
  End;
end;

A multi-slot PKCS#11 card, common with Polish qualified-signature cards that put separate certificates behind separate PINs, can now be inventoried without any PIN at all. EnumerateCertificateListAllSlots reads every slot, each entry recording where it came from, and TokenSlotCount reports how many slots actually hold a token.

Certificates themselves also report everything they carry rather than the seven attributes the parser used to recognise, with the postal address decoded into readable lines, and any attribute reachable by its OID.

Signatures That Stay Checkable

PAdES gains two profiles. spPAdESBasicT signs with an embedded timestamp and no revocation data, and spPAdESDocumentArchive adds an archive timestamp on top of the long term profile, covering the whole document including its revocation data, so the file stays checkable once the first timestamp's own validity window has passed.

Finding the issuing certificate used to be your problem, because most qualified signature cards carry only your own certificate. New GetIssuerCertificate and GetCertificateChain calls find the certificate that issued the one you are signing with, and the whole path above it, matching cryptographically rather than by name, so an authority that has changed its signing key is not confused with its predecessor. IssuerLookup decides where to search: the Windows certificate store by default, PEM or DER files you ship, or the address inside the certificate, which is off by default.

Timestamp requests can now be signed, which some qualified authorities require, the Polish ones in particular. Set RequestFormat to trfCMS, assign a key provider, and OnBeforeSendRequest and OnAfterReceiveResponse hand you the exact bytes sent and received.

Verification With Trust Anchors

This is the change to read carefully. Until now the verifier took the signing certificate out of the document it was checking and confirmed that key had signed that document, which proves only that whoever wrote the document also wrote the signature in it.

New TrustedCertificates and TrustedCertificateStore properties say which roots you trust, and RequireTrustedChain, CheckKeyUsage and RequireCompleteRevocationCheck decide how strict the outcome is. An anchor is matched by SHA-256 thumbprint or by verifying under its own key, never by name. A verifier with no anchor returns the verdict it returned before, but the ETSI TS 119 102-2 report no longer says total-passed for a signature that was never chained to an anchor, so stored reports produced without an anchor need regenerating.

Authenticode signing can now embed extra certificates, the same thing signtool /ac does, so a kernel-mode driver signature can chain to the Microsoft Code Verification Root through its cross-certificate.

One HTTP Transport, With Proxies

Every request the library makes now goes through one transport with a shared HTTPOptions property: the timestamp client, the OCSP and revocation list clients, the EU trust list download and the cloud key providers. It carries the proxy, which can be the system one, none, an explicit address or the one the machine resolves per address, the credentials for a proxy that asks for them, the client certificate, the lowest accepted TLS version and the user agent. Every setting defaults to what those requests did before, and a new OnHTTPRequest event replaces the transport entirely for a gateway these settings cannot describe.

The Signing Server

API keys and the users who create them are now isolated per project, so a project admin manages the keys in their own project without seeing anyone else's. Keys can be enabled and disabled rather than only revoked one way, their rate limit and daily quota can be edited after creation, and the audit log can finally be filtered by client address, both in the console and in the CSV export.

An Authenticode signature can now carry more than two nested signatures with a different certificate for each, which is what shipping one file signed by an expiring certificate and its replacement needs. Windows catalog files can be signed, and a new /api/v1/sign/raw endpoint signs a digest you have already computed and returns only the signature value, which is exactly what signtool asks for through its /dlib callback. Since it will sign any digest handed to it, it is off by default and turned on one provider at a time.

Several server defaults changed for good reason. The server.listen setting now actually binds the address you gave it, administration actions require a form post carrying a one time token, and webhook deliveries over HTTPS check the certificate of the address they are sent to. All three are in the upgrade notes.

sgcIndy 2026.9.0

A small release, and every item in it is a fix worth having.

The protocol name agreed through ALPN was written with four bytes into the single byte OpenSSL keeps for its length, so every handshake that negotiated a protocol overwrote the memory next to it. A TLS server on the IOCP or EPOLL engine used all the CPU of one thread when the other side asked to renegotiate, retrying the write again and again instead of sending what OpenSSL had already prepared. On Linux, a TLS application was closed by the operating system when the other side reset the connection during a write, because OpenSSL writes in a way that cannot ask the system to suppress the broken pipe signal. The same problem under FPC and Lazarus is fixed too, including server-accepted connections on macOS, which were still exposed.

For the Community setup, the precompiled Delphi 13 binaries were produced from the Delphi 12 projects, so they carried the 290 names instead of the 370 names the Delphi 13 installer expects, and Windows ARM64EC was not built at all. Delphi 13 now builds its own projects for every supported platform.

sgcOpenAPI 2026.9.0

The parser stopped failing quietly. Every document now comes back with a Warnings list holding a missing openapi or info member, a member with the wrong JSON type, an operation that could not be generated, an unresolved path item reference and any JSON Schema keyword that is read but not yet honoured.

uses
  sgcOpenAPI_Classes, sgcOpenAPI_Parser_Client_Pascal;

var
  oParser: TsgcOpenAPI_Parser_Client_Pascal;
  i: Integer;
begin
  oParser := TsgcOpenAPI_Parser_Client_Pascal.Create;
  Try
    oParser.OpenAPIClassName := 'TPetStoreClient';
    oParser.OutputFileName := 'PetStoreClient.pas';

    oParser.ReadFromFile('petstore.json');

    for i := 0 to oParser.Warnings.Count - 1 do
      Memo1.Lines.Add('warning: ' + oParser.Warnings[i]);

    oParser.SaveToFile('PetStoreClient.pas');
  Finally
    oParser.Free;
  End;
end;

It also knows which version of the specification it is reading, so a 3.0 document and a 3.1 document are no longer treated as the same thing, exclusiveMinimum and exclusiveMaximum being the obvious case. OpenAPI 3.1 webhooks, jsonSchemaDialect, components.pathItems, the license identifier, the mutualTLS security scheme, a type declared as an array such as ["string","null"] and a schema declared as a boolean are all supported, and so are the OpenAPI 3.2 query operation and the additionalOperations map.

Generation got better in the places it used to give up. An inline object schema now generates its own class instead of degrading to a string, items is read as a complete schema, and the generated client supports cookie parameters and the full parameter serialization rules: matrix, label, simple, form, spaceDelimited, pipeDelimited and deepObject, with explode and allowReserved.

The command line is finally usable in a build script. It sets an exit code, 0 on success and 1 to 7 for the different failures, and errors always go to standard error. A new -r switch converts a YAML or Swagger 2.0 document through the public converter, off by default.

The same OpenAPI improvements ship inside sgcWebSockets, where the server-side validator now also checks header and cookie parameters, with ValidateHeaderParams, ValidateCookieParams and EnforceRequired.

Before You Upgrade

Every product has breaking changes this time. These are the ones most likely to reach you.

The full list, with the reason for each one, is in each product's changelog.

Getting It

Release 2026.9.0 is available now, with full source code and one year of updates, for Delphi 7 through Delphi 13 Florence, the matching C++ Builder versions, and .NET.

sgcWebSockets · sgcWebRTC · sgcCrypto · sgcHTML · sgcSign · sgcIndy · sgcOpenAPI

Download the trial · Changelog

Questions or feedback? Get in touch, you will get a reply from the people who wrote the code.