Skip to content

Commit 48e3852

Browse files
committed
Enhance MCCP4 compression handling: implement fallback mode for empty payloads, support multiple encodings, and improve error handling for Zstandard and deflate initialization. Update comments for better clarity.
1 parent 544863f commit 48e3852

1 file changed

Lines changed: 154 additions & 20 deletions

File tree

mccp4.cpp

Lines changed: 154 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,33 +75,143 @@ void CMUSHclientDoc::CleanupZstd()
7575
m_zstd_outcap = 0;
7676
}
7777

78-
// Handle TELOPT_COMPRESS4 subnegotiation (like MCCP2)
78+
// Handle TELOPT_COMPRESS4 subnegotiation with new protocol (the more future proof standard)
7979
void CMUSHclientDoc::Handle_TELOPT_COMPRESS4()
8080
{
8181
CString strMessage;
82-
// MCCP4: Received IAC SB COMPRESS4 IAC SE - starting compression
83-
m_iMCCP_type = 4; // MCCP v4 (Zstandard)
8482

85-
// Initialize compression library if not already done
86-
if (!InitZstd())
83+
// Check if we have subnegotiation data
84+
if (m_IAC_subnegotiation_data.empty())
8785
{
88-
strMessage = Translate("Cannot initialize Zstandard decompression. World closed.");
89-
OnConnectionDisconnect(); // close the world
90-
UMessageBox(strMessage, MB_ICONEXCLAMATION);
86+
// Fallback mode: empty payload means "start zstd now" (backward compatibility)
87+
TRACE("MCCP4: Fallback mode - empty payload, starting zstd\n");
88+
m_MCCP4_active_encoding = "zstd";
89+
90+
if (!InitZstd())
91+
{
92+
strMessage = Translate("Cannot initialize Zstandard decompression. World closed.");
93+
OnConnectionDisconnect();
94+
UMessageBox(strMessage, MB_ICONEXCLAMATION);
95+
return;
96+
}
97+
98+
size_t const resetResult = ZSTD_DCtx_reset((ZSTD_DCtx*)m_zstd_dstream, ZSTD_reset_session_only);
99+
if (ZSTD_isError(resetResult))
100+
{
101+
strMessage = Translate("Cannot reset Zstandard decompression stream. World closed.");
102+
OnConnectionDisconnect();
103+
UMessageBox(strMessage, MB_ICONEXCLAMATION);
104+
return;
105+
}
106+
107+
m_iMCCP_type = 4;
108+
m_bCompress = true;
109+
m_MCCP4_negotiation_active = false;
91110
return;
92111
}
93112

94-
// Reset the decompression stream for new data
95-
size_t const resetResult = ZSTD_DCtx_reset((ZSTD_DCtx*)m_zstd_dstream, ZSTD_reset_session_only);
96-
if (ZSTD_isError(resetResult))
113+
// Parse subnegotiation data
114+
unsigned char suboption = (unsigned char)m_IAC_subnegotiation_data[0];
115+
116+
switch (suboption)
97117
{
98-
strMessage = Translate("Cannot reset Zstandard decompression stream. World closed.");
99-
OnConnectionDisconnect(); // close the world
100-
UMessageBox(strMessage, MB_ICONEXCLAMATION);
101-
return;
118+
case MCCP4_BEGIN_ENCODING:
119+
{
120+
// Extract encoding name from data
121+
if (m_IAC_subnegotiation_data.size() < 2)
122+
{
123+
TRACE("MCCP4: Invalid BEGIN_ENCODING - no encoding name\n");
124+
Send_IAC_DONT(TELOPT_COMPRESS4);
125+
return;
126+
}
127+
128+
string encoding = m_IAC_subnegotiation_data.substr(1);
129+
TRACE1("MCCP4: BEGIN_ENCODING %s\n", encoding.c_str());
130+
131+
// Check if we support this encoding
132+
if (m_MCCP4_accepted_encodings.find(encoding) == string::npos)
133+
{
134+
strMessage.Format("MCCP4: Server chose unsupported encoding '%s'. Aborting compression.", encoding.c_str());
135+
TRACE1("%s\n", strMessage);
136+
Send_IAC_DONT(TELOPT_COMPRESS4);
137+
return;
138+
}
139+
140+
// Initialize the appropriate codec
141+
if (encoding == "zstd")
142+
{
143+
if (!InitZstd())
144+
{
145+
strMessage = Translate("Cannot initialize Zstandard decompression. World closed.");
146+
OnConnectionDisconnect();
147+
UMessageBox(strMessage, MB_ICONEXCLAMATION);
148+
return;
149+
}
150+
151+
size_t const resetResult = ZSTD_DCtx_reset((ZSTD_DCtx*)m_zstd_dstream, ZSTD_reset_session_only);
152+
if (ZSTD_isError(resetResult))
153+
{
154+
strMessage = Translate("Cannot reset Zstandard decompression stream. World closed.");
155+
OnConnectionDisconnect();
156+
UMessageBox(strMessage, MB_ICONEXCLAMATION);
157+
return;
158+
}
159+
m_iMCCP_type = 4;
160+
}
161+
else if (encoding == "deflate")
162+
{
163+
// Use existing MCCP2 deflate infrastructure
164+
if (!m_bCompressInitOK && !m_bCompress)
165+
m_bCompressInitOK = InitZlib(m_zCompress);
166+
167+
// Allocate compression buffers if not already allocated
168+
if (!m_CompressOutput)
169+
m_CompressOutput = (Bytef *) malloc (m_nCompressionOutputBufferSize);
170+
if (!m_CompressInput)
171+
m_CompressInput = (Bytef *) malloc (COMPRESS_BUFFER_LENGTH);
172+
173+
if (!(m_bCompressInitOK && m_CompressOutput && m_CompressInput))
174+
{
175+
strMessage = Translate("Cannot initialize deflate decompression. World closed.");
176+
OnConnectionDisconnect();
177+
UMessageBox(strMessage, MB_ICONEXCLAMATION);
178+
return;
179+
}
180+
181+
int izError = inflateReset(&m_zCompress);
182+
if (izError != Z_OK)
183+
{
184+
strMessage = Translate("Cannot reset deflate decompression stream. World closed.");
185+
OnConnectionDisconnect();
186+
UMessageBox(strMessage, MB_ICONEXCLAMATION);
187+
return;
188+
}
189+
m_iMCCP_type = 4; // Still MCCP4, but using deflate
190+
}
191+
else
192+
{
193+
strMessage.Format("MCCP4: Unknown encoding '%s'. Aborting compression.", encoding.c_str());
194+
TRACE1("%s\n", strMessage);
195+
Send_IAC_DONT(TELOPT_COMPRESS4);
196+
return;
197+
}
198+
199+
m_MCCP4_active_encoding = encoding;
200+
m_bCompress = true;
201+
m_MCCP4_negotiation_active = false;
202+
TRACE1("MCCP4: Compression active with encoding %s\n", encoding.c_str());
203+
break;
204+
}
205+
206+
default:
207+
{
208+
// Unknown suboption - send WONT and continue (non-fatal)
209+
unsigned char wont_response[] = { IAC, SB, TELOPT_COMPRESS4, MCCP4_WONT, suboption, IAC, SE };
210+
SendPacket(wont_response, sizeof wont_response);
211+
TRACE1("MCCP4: Unknown suboption %d - sent WONT\n", (int)suboption);
212+
break;
213+
}
102214
}
103-
104-
m_bCompress = true; // MCCP4: Zstandard decompression successfully activated
105215
}
106216

107217
// Process Zstandard compressed data
@@ -222,10 +332,34 @@ int CMUSHclientDoc::ProcessZstdCompressed(const unsigned char* input, unsigned i
222332

223333
if (frameEnded)
224334
{
225-
// Reset DCtx for potential future COMPRESS4 start; exit MCCP mode now.
335+
// Reset decoder state
226336
ZSTD_DCtx_reset((ZSTD_DCtx*)m_zstd_dstream, ZSTD_reset_session_only);
227-
m_bCompress = false;
228-
m_iMCCP_type = 0;
337+
338+
// Check if remaining bytes in this buffer are raw telnet
339+
if (inBuf.pos < inBuf.size)
340+
{
341+
unsigned char nextByte = ((unsigned char*)inBuf.src)[inBuf.pos];
342+
if (nextByte == 0xFF) // IAC - raw telnet follows in same buffer
343+
{
344+
// Disable compression so caller processes remaining bytes as raw telnet
345+
m_bCompress = false;
346+
m_iMCCP_type = 0;
347+
TRACE("MCCP4: Frame ended with IAC in buffer - disabled compression\n");
348+
}
349+
else
350+
{
351+
// More compressed data follows - keep compression active for concatenated frames
352+
TRACE("MCCP4: Frame ended with %d more bytes - keeping compression active for concatenated frames\n",
353+
(int)(inBuf.size - inBuf.pos));
354+
// Keep compression active and process remaining bytes as next frame
355+
// Don't return early - let normal flow handle it
356+
}
357+
}
358+
else
359+
{
360+
// No remaining bytes - keep compression active for next packet
361+
TRACE("MCCP4: Frame ended - keeping compression active for next packet\n");
362+
}
229363
}
230364

231365
// Tell the caller how many INPUT bytes we consumed,

0 commit comments

Comments
 (0)