/* * Client decompression module for the mud client compression protocol. * See http://homepages.ihug.co.nz/~icecube/compress/ for more details. * * mccpDecompress.c - module code. Link this with your client code. * * Oliver Jowett . Demangle address as needed. * * This code is placed in the public domain. * */ /* Modified: 20000530 */ /* See mccp.h for API information */ #include #include #include #include "config.h" #ifdef MUDCOMPRESS #include #include "mccp.h" /* Telnet values we're interested in */ #define IAC 255 #define DONT 254 #define DO 253 #define WONT 252 #define WILL 251 #define SB 250 #define SE 240 #define TELOPT_COMPRESS 85 #define TELOPT_COMPRESS2 86 /* We say DO COMPRESS2 to WILL COMPRESS2, then DONT COMPRESS to WILL COMPRESS * -or- * We say DO COMPRESS to WILL COMPRESS if it arrives before any COMPRESS2. * * Later the server sends IAC SB COMPRESS IAC SE (v2) or IAC SB COMPRESS WILL * SE (v1), and immediately following * that, begins compressing * * Compression ends on a Z_STREAM_END, no other marker is used */ static char will_v1[] = { IAC, WILL, TELOPT_COMPRESS, 0 }; static char do_v1[] = { IAC, DO, TELOPT_COMPRESS, 0 }; static char dont_v1[] = { IAC, DONT, TELOPT_COMPRESS, 0 }; static char on_v1[] = { IAC, SB, TELOPT_COMPRESS, WILL, SE, 0 }; static char will_v2[] = { IAC, WILL, TELOPT_COMPRESS2, 0 }; static char do_v2[] = { IAC, DO, TELOPT_COMPRESS2, 0 }; static char on_v2[] = { IAC, SB, TELOPT_COMPRESS2, IAC, SE, 0 }; /* "Opaque" state object */ struct mc_state_s { z_stream *stream; /* stream we're using */ unsigned char *inbuf; /* input buffer (data from mud) */ unsigned int insize; /* .. and how much is used */ unsigned int inalloc; /* .. and how much is allocated */ unsigned char *outbuf; /* output buffer (data to user) */ unsigned int outsize; /* .. and how much is used */ unsigned int outalloc; /* .. and how much is allocated */ int error; int resp_v1; /* waiting to send IAC DO/DONT COMPRESS */ int resp_v2; /* waiting to send IAC DO COMPRESS2 */ int got_v2; /* responded to a IAC WILL COMPRESS2 already */ unsigned long comp; unsigned long uncomp; }; /* Initialise a new state object */ mc_state *mudcompress_new(void) { mc_state *state; state = malloc(sizeof(*state)); state->stream = NULL; /* Not decompressing */ state->inalloc = 2048; state->outalloc = 2048; state->inbuf = malloc(state->inalloc); state->outbuf = malloc(state->outalloc); state->insize = 0; state->outsize = 0; state->error = 0; state->comp = 0; state->uncomp = 0; state->resp_v1 = 0; state->resp_v2 = 0; state->got_v2 = 0; return state; } /* Clean up a state object */ void mudcompress_delete(mc_state *state) { if (state->stream) { inflateEnd(state->stream); free(state->stream); } free(state->inbuf); free(state->outbuf); free(state); } /* zlib helpers */ static void *zlib_alloc(void *opaque, unsigned int items, unsigned int size) { return calloc(items, size); } static void zlib_free(void *opaque, void *address) { free(address); } static void grow_inbuf(mc_state *state, int needed) { int old = state->inalloc; while (state->inalloc < state->insize + needed) state->inalloc *= 2; if (old != state->inalloc) state->inbuf = realloc(state->inbuf, state->inalloc); } static void grow_outbuf(mc_state *state, int needed) { int old = state->outalloc; while (state->outalloc < state->outsize + needed) state->outalloc *= 2; if (old != state->outalloc) state->outbuf = realloc(state->outbuf, state->outalloc); } static void decompress_inbuf(mc_state *state) { int status; /* We are now decompressing from inbuf to outbuf */ if (!state->insize) return; /* nothing to decompress? */ state->stream->next_in = state->inbuf; state->stream->next_out = state->outbuf + state->outsize; state->stream->avail_in = state->insize; state->stream->avail_out = state->outalloc - state->outsize; status = inflate(state->stream, Z_PARTIAL_FLUSH); if (status == Z_OK || status == Z_STREAM_END) { /* Successful decompression */ /* Remove used data from inbuf */ state->comp += state->insize - state->stream->avail_in; state->uncomp += state->stream->next_out - state->outbuf; memmove(state->inbuf, state->stream->next_in, state->stream->avail_in); state->insize = state->stream->avail_in; /* Update outbuf pointers */ state->outsize = state->stream->next_out - state->outbuf; /* Done */ if (status == Z_STREAM_END) { /* Turn off compression too */ grow_outbuf(state, state->insize); memcpy(state->outbuf + state->outsize, state->inbuf, state->insize); state->outsize += state->insize; state->insize = 0; inflateEnd(state->stream); free(state->stream); state->stream = NULL; } return; } if (status == Z_BUF_ERROR) { /* Full buffers? Maybe we need more output space.. */ if (state->outsize * 2 > state->outalloc) { grow_outbuf(state, state->outalloc); decompress_inbuf(state); } return; } /* Error */ state->error = 1; } /* We received some data */ void mudcompress_receive(mc_state *state, const char *data, unsigned len) { int i; if (state->error) return; if (!state->stream) { int residual = -1; int clen; /* Just copy to outbuf. Also copy any residual inbuf */ grow_outbuf(state, len + state->insize); memcpy(state->outbuf + state->outsize, data, len); state->outsize += len; memcpy(state->outbuf + state->outsize, state->inbuf, state->insize); state->outsize += state->insize; state->insize = 0; /* Check for Magic Marker. ugh this is messy */ for (i=0; i < state->outsize; i++) { if (state->outbuf[i] == IAC) { if (i + 1 < state->outsize && state->outbuf[i+1] == IAC) { /* IAC IAC - ignore */ i++; continue; } clen = (i + strlen(will_v1) < state->outsize) ? strlen(will_v1) : state->outsize - i; if (!memcmp(&state->outbuf[i], will_v1, clen)) { if (clen != strlen(will_v1)) { /* Partial match. Save it. */ residual = i; break; } /* If we got WILL COMPRESS2 then refuse COMPRESS, otherwise * accept it */ if (state->got_v2) state->resp_v1 = -1; else state->resp_v1 = 1; memmove(&state->outbuf[i], &state->outbuf[i + strlen(will_v1)], state->outsize - strlen(will_v1)); state->outsize -= strlen(will_v1); i--; continue; } if (!memcmp(&state->outbuf[i], will_v2, clen)) { if (clen != strlen(will_v2)) { /* Partial match. Save it. */ residual = i; break; } state->resp_v2 = 1; state->got_v2 = 1; memmove(&state->outbuf[i], &state->outbuf[i + strlen(will_v2)], state->outsize - strlen(will_v2)); state->outsize -= strlen(will_v2); i--; continue; } clen = (i + strlen(on_v1) < state->outsize) ? strlen(on_v1) : state->outsize - i; if ((!memcmp(&state->outbuf[i], on_v1, clen) && !state->got_v2) || (!memcmp(&state->outbuf[i], on_v2, clen) && state->got_v2)) { if (clen != strlen(on_v1)) { /* Partial match. Save it. */ residual = i; break; } /* Switch to compression */ /* copy any compressible bits to our inbuf */ grow_inbuf(state, state->outsize - i - strlen(on_v1)); memcpy(state->inbuf, state->outbuf + i + strlen(on_v1), state->outsize - i - strlen(on_v1)); state->insize = state->outsize - i - strlen(on_v1); /* clean up our output buffer */ state->outsize = i; /* init stream */ state->stream = malloc(sizeof(z_stream)); state->stream->zalloc = zlib_alloc; state->stream->zfree = zlib_free; state->stream->opaque = NULL; if (inflateInit(state->stream) != Z_OK) { state->error = 1; free(state->stream); state->stream = NULL; return; } /* Continue with decompression */ break; } } } if (!state->stream) { /* didn't start decompressing? */ /* We might have some residual, copy to inbuf for later checking */ if (residual != -1) { grow_inbuf(state, state->outsize - residual); memcpy(state->inbuf + state->insize, state->outbuf + residual, state->outsize - residual); state->outsize = residual; } return; } } else { /* New data to decompress. Copy to inbuf */ grow_inbuf(state, len); memcpy(state->inbuf + state->insize, data, len); state->insize += len; } decompress_inbuf(state); } /* How much data is available? */ int mudcompress_pending(mc_state *state) { return state->error ? 0 : state->outsize; } /* Was there an error? */ int mudcompress_error(mc_state *state) { return state->error; } /* Get some data */ int mudcompress_get(mc_state *state, char *buf, int size) { int copied; if (state->error || !state->outsize) return 0; if (size > state->outsize) copied = state->outsize; else copied = size; memcpy(buf, state->outbuf, copied); state->outsize -= copied; if (state->outsize) memmove(state->outbuf, state->outbuf + copied, state->outsize); /* Do some more decompression */ decompress_inbuf(state); return copied; } void mudcompress_stats(mc_state *state, unsigned long *comp, unsigned long *uncomp) { *comp = state->comp; *uncomp = state->uncomp; } const char *mudcompress_response(mc_state *state) { if (state->resp_v1 == 1) { state->resp_v1 = 0; return do_v1; } if (state->resp_v1 == -1) { state->resp_v1 = 0; return dont_v1; } if (state->resp_v2) { state->resp_v2 = 0; return do_v2; } return NULL; } int mudcompress_compressing(mc_state *state) { return (state->stream != NULL); } int mudcompress_v2(mc_state *state) { return (state->stream != NULL && state->got_v2); } #endif /* MUDCOMPRESS */