@@ -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 )
7979void 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