v2.3.7-beta
Wasabi v2.3.7-beta
This release delivers a comprehensive structural overhaul focused on Winsock spec compliance, memory safety, x64 pointer correctness, MQTT v5 protocol alignment, and API naming consistency. It is not a feature release. Every change addresses a correctness or stability defect that could manifest silently in production workbooks.
Bug Fixes
Winsock Core and Asynchronous I/O
FeedBuffer: Exhaustive FD_READ draining via FIONREAD loop
The previous implementation read up to 64 KB per FD_READ event and returned. This violated the level-triggered contract of WSAAsyncSelect: if bytes remained in the kernel buffer after a partial read, Windows would not fire a new FD_READ event, causing the connection to silently stall until the next polling cycle. FeedBuffer is now a closed Do loop driven by ioctlsocket(FIONREAD) that continues reading until the kernel reports zero available bytes, fully satisfying the Winsock specification.
RawSendFor: Graceful handling of partial send() writes
The send path now loops until all bytes in a frame are flushed to the kernel, correctly handling partial writes that can occur when the socket send buffer is under pressure during large transfers.
ResolveAndConnect: IPv6 address truncation on x64
An incorrect CLng conversion was silently truncating IPv6 addresses whose pointer values exceeded 2 GB on 64-bit hosts. The affected code path now applies the correct 64-bit mask (&HFFFFFFFF^) via getaddrinfo, making IPv6 resolution reliable under VBA7.
TLS and SSPI
GenerateNtlmToken: Corrupt NTLM token decoding
StrConv was being used to decode Base64-encoded NTLM challenge tokens before passing them to InitializeSecurityContext. StrConv applies codepage transformations that corrupt arbitrary binary data. The function now uses CryptStringToBinaryW from crypt32.dll to decode tokens byte-for-byte as intended by the SSPI contract.
DoTLSHandshake: Buffer overflow on oversized server certificates
If a server delivered a certificate chain whose total size exceeded 32 KB across fragmented SEC_E_INCOMPLETE_MESSAGE reads, the internal receive buffer could overflow. The handshake loop now performs a bounds-checked ReDim Preserve on the accumulation buffer before each sock_recv call, allowing it to handle arbitrarily large certificate chains without risk.
CloseSession: Double-free of SecHandle and CredHandle resources
Disconnect paths could invoke FreeCredentialsHandle and DeleteSecurityContext on already-released handles when CloseSession was called through multiple code paths for the same connection. The cleanup routine is now routed exclusively through CleanupHandle, which performs a single guarded release of both handles and nullifies the pointers on exit, eliminating crashes in EXCEL.EXE caused by double-free.
HTTP proxy tunnel: Silent auth failure on second NTLM pass
The second-pass NTLM credential submission was not validating the server's response code. A rejected credential could produce a non-200 response that was silently interpreted as a successful tunnel, causing the subsequent TLS handshake to fail with a confusing SSPI error. The tunnel validation now strictly checks for 200 before proceeding.
WebSocket Protocol (RFC 6455)
Close frame: Malformed header for payloadless close
Sending a Close frame with no reason string (the common case when disconnecting with code 1000 and no reason) incorrectly set the payload length field to 2, implying that two bytes of payload followed. The correct encoding for a code-only close is a 4-byte frame: FIN+opcode, mask+length=2, and the 4-byte masking key with the two status code bytes XOR-masked against it. Wasabi was emitting the header with length=2 but constructing the body as if length=0, producing a malformed frame that caused NGINX and HAProxy to drop the client. The close frame builder now branches correctly on payload length.
Offline queue: Unbounded memory growth (OOM protection)
OfflineQueueEnabled grew the internal text and binary offline queues without limit. Under a sustained network outage with active sends, this would consume all available process memory. Both queues are now hard-capped at 10,000 entries. Attempts to enqueue beyond this limit emit a log warning and drop the message rather than expanding the buffer.
MQTT v5
MqttSubscribe / MqttUnsubscribe: Missing Property Length byte causing broker timeout
MQTT v5 requires a Property Length varint field in the variable header of SUBSCRIBE and UNSUBSCRIBE packets, even when no properties are present. The missing 0x00 byte caused compliant brokers such as HiveMQ to parse the packet incorrectly, producing a timeout instead of a SUBACK. The byte is now correctly injected.
MqttDecodeVarInt: Truncated VarInt multiplier
The VarInt decoder used an incorrect upper bound for the multiplier loop, limiting readable packet sizes to well below the MQTT v5 theoretical maximum of 268,435,455 bytes. The loop guard is now multiplier > 268435456, restoring full protocol range.
Memory Safety and x64 Pointer Correctness
WasabiAsyncWndProc: lParam read width mismatch on x64
The async window procedure was extracting the connection handle from lParam using a 4-byte CopyMemory call in a context where lParam is 8 bytes on a 64-bit host. This produced undefined behavior and potential handle misidentification. The extraction now uses a bitmask (CLng(lParam And &HFFFFFFFF&)) that is safe on both 32-bit and 64-bit targets.
TickDiff: Signed overflow on long-running connections
Subtracting two GetTickCount values in a Long context produced negative results after approximately 24.9 days of uptime, causing inactivity timeout and ping scheduling logic to malfunction. The computation now casts through CDbl with a 32-bit mask before comparison, making it overflow-safe for arbitrarily long connections.
BuildWSFrame: Uninitialized array guard before CopyMemory
A CopyMemory call into the frame buffer could be reached before the target array was dimensioned in certain zero-length payload paths, producing a runtime error. An initialization guard now ensures the buffer is allocated before any memory copy.
BuildWSFrame: payloadLen = 127 overflow protection
A payload length of exactly 127 triggered the 16-bit extended length path, which is incorrect: 127 is a reserved value in the WebSocket framing spec and the extended length path begins at 126. A guard now prevents this case from producing a malformed frame header.
WebSocketSendBatch / WebSocketSendBatchBinary: flushBuf scope instability
The flush buffer variable was declared at a scope that could be uninitialized if the payload array was empty, causing an error on the first ReDim or CopyMemory. The variable is now scoped and initialized at the top of the batch loop before any conditional branch.
API Changes
The following identifiers have been renamed for naming consistency and clarity. All old names have been removed. See the Migration Guide below for a complete mapping.
Symmetry between binary and text variants:
TcpSend is now TcpSendBinary. TcpReceive is now TcpReceiveBinary. TcpBroadcast is now TcpBroadcastBinary. WebSocketBroadcast is now WebSocketBroadcastText. WebSocketSendMTUAware is now WebSocketSendTextMTUAware.
Semantic corrections:
WebSocketGetErrorDescription is now WasabiGetErrorDescription. The previous name was misleading because the function reports errors from TCP and MQTT contexts in addition to WebSocket. WebSocketSetBufferSizes is now WebSocketSetBufferSize (singular). MqttPingReq is now MqttSendPing, hiding internal MQTT protocol terminology from the public API surface.
Win32 declaration reorganization:
All Declare Function / Declare Sub statements have been reorganized into named groups by source library (advapi32, bcrypt, crypt32, kernel32, secur32, user32, winhttp, ws2_32) and sorted alphabetically within each group. This is an internal change with no effect on behavior.
Migration Guide
The table below covers every identifier that changed in this release. No other public API surface was modified.
| Previous name | Current name | Notes |
|---|---|---|
TcpSend |
TcpSendBinary |
Raw byte array send on TCP handles |
TcpReceive |
TcpReceiveBinary |
Raw byte array receive on TCP handles |
TcpBroadcast |
TcpBroadcastBinary |
Byte array broadcast to all TCP handles |
WebSocketBroadcast |
WebSocketBroadcastText |
Text broadcast to all WebSocket handles |
WebSocketSendMTUAware |
WebSocketSendTextMTUAware |
MTU-fragmented text send |
WebSocketGetErrorDescription |
WasabiGetErrorDescription |
Error description covering TCP, WebSocket, and MQTT |
WebSocketSetBufferSizes |
WebSocketSetBufferSize |
Receive and fragment buffer size configuration |
MqttPingReq |
MqttSendPing |
MQTT PINGREQ keep-alive |
All renamed functions carry identical signatures. A project-wide find-and-replace on each old name is sufficient to migrate. No parameter changes, no behavioral changes.
Notes
This release carries the -beta suffix because the async thunk memory model has not yet been validated under all VBA host configurations (Excel 32-bit, Excel 64-bit, Access, and VB6). The core TCP, WebSocket, and MQTT paths are considered stable. The async subsystem (WasabiUseAsync, WasabiAsyncWndProc) should be treated as production-candidate but not production-certified until the next tagged release.
If you encounter any regression introduced by this release, please open an issue with the error code from WasabiGetErrorDescription and the value of WebSocketGetTechnicalDetails or TcpGetTechnicalDetails.