These code fragments come from Abandoned Reality. They will only really be useful as a guideline for implementing compression in another mud server.
AR is written in C++ - so expect C++ syntax.
The Descriptor class represents a client connection. First, the easy bits - class data definitions, destructor. Note that out_compress / out_compress_buf are set to NULL on construction (not shown).
#include <zlib.h>
class Descriptor
{
// ...
z_stream *out_compress;
unsigned char *out_compress_buf;
};
Descriptor::~Descriptor()
{
// ...
if (out_compress_buf)
free_mem(out_compress_buf);
if (out_compress) {
deflateEnd(out_compress);
free_mem(out_compress);
}
}
Now for the actual output code.
/*
* Low level output function.
*/
bool Descriptor::processOutput (bool fPrompt)
{
char *buf;
// ...
// At this point, 'buf' contains 'outtop' bytes of uncompressed data to
// write to the client.
/*
* OS-dependent output.
*
* Now behaves a bit more nicely -Nemon
*
*/
if (outtop)
{
int count= write (buf, outtop);
if (!count)
{
outtop = 0;
last_errno = errno;
return false;
}
if (count < outtop)
memmove(outbuf, outbuf+count, outtop - count);
outtop -= count;
}
// Maybe do some compressed output too
if (!processCompressed()) {
return false;
}
return true;
}
// Since compression has another buffer effectively invisible to the
// main system, this gets called whenever a compressed connection is
// writable but has no "normal" pending output - to try to flush any
// partial compression bits
bool Descriptor::processCompressed(void)
{
if (!out_compress)
return true;
int iStart, nBlock, nWrite;
// Try to write out some data..
int len = out_compress->next_out - out_compress_buf;
if (len > 0) {
// we have some data to write
for (iStart = 0; iStart < len; iStart += nWrite)
{
nBlock = UMIN (len - iStart, 4096);
if ((nWrite = ::write (descriptor, out_compress_buf + iStart, nBlock)) < 0)
{
if (errno == EAGAIN ||
errno == ENOSR)
break;
last_errno = errno;
return false; // write error
}
if (!nWrite)
break;
stats.reboot.bytes_out += nWrite;
stats.ever.bytes_out += nWrite;
stats.reboot.comp_out += nWrite;
stats.ever.comp_out += nWrite;
}
if (iStart) {
// We wrote "iStart" bytes
if (iStart < len)
memmove(out_compress_buf, out_compress_buf+iStart, len - iStart);
out_compress->next_out = out_compress_buf + len - iStart;
}
}
return true;
}
/*
* Lowest level output function.
* Write a block of text to the file descriptor.
* If this gives errors on very long blocks (like 'ofind all'),
* try lowering the max block size.
*
* Now behaves more nicely -Nemon
*/
int Descriptor::write (const char *txt, int length)
{
int iStart;
int nWrite;
int nBlock;
if (length <= 0)
length = strlen(txt);
// Check for output compression
if (out_compress) {
out_compress->next_in = (unsigned char *)txt;
out_compress->avail_in = length;
while (out_compress->avail_in) {
out_compress->avail_out = COMPRESS_BUF_SIZE - (out_compress->next_out - out_compress_buf);
if (out_compress->avail_out) {
int status = deflate(out_compress, Z_SYNC_FLUSH);
if (status != Z_OK) {
// Boom
return 0;
}
}
// Try to write out some data..
int len = out_compress->next_out - out_compress_buf;
if (len > 0) {
// we have some data to write
for (iStart = 0; iStart < len; iStart += nWrite)
{
nBlock = UMIN (len - iStart, 4096);
if ((nWrite = ::write (descriptor, out_compress_buf + iStart, nBlock)) < 0)
{
if (errno == EAGAIN ||
errno == ENOSR)
break;
return 0; // write error
}
if (!nWrite)
break;
stats.reboot.bytes_out += nWrite;
stats.ever.bytes_out += nWrite;
stats.reboot.comp_out += nWrite;
stats.ever.comp_out += nWrite;
}
if (!iStart)
break; // Can't write any more
// We wrote "iStart" bytes
if (iStart < len)
memmove(out_compress_buf, out_compress_buf+iStart, len - iStart);
out_compress->next_out = out_compress_buf + len - iStart;
}
// Loop
}
// Done.
stats.reboot.uncomp_out += length - out_compress->avail_in;
stats.ever.uncomp_out += length - out_compress->avail_in;
return length - out_compress->avail_in;
}
for (iStart = 0; iStart < length; iStart += nWrite)
{
nBlock = UMIN (length - iStart, 4096);
if ((nWrite = ::write (descriptor, txt + iStart, nBlock)) < 0)
{
if (errno == EAGAIN ||
errno == ENOSR)
return iStart;
return 0;
}
if (!nWrite)
return iStart;
if (connected != CON_INTERCOM) {
stats.reboot.bytes_out += nWrite;
stats.ever.bytes_out += nWrite;
}
}
return iStart;
}
Next, methods to start and end compression, and a user command to force the change.
// zlib alloc/free stuff
void *zlib_alloc(void *opaque, unsigned int items, unsigned int size)
{
return alloc_mem(items * size);
}
void zlib_free(void *opaque, void *address)
{
free_mem(address);
}
void do_compress(Character *ch, const char *argument, Character *vic)
{
if (!ch->desc) {
ch->printf("What descriptor?!\n");
return;
}
if (!ch->desc->out_compress) {
if (!ch->desc->startCompression()) {
ch->printf("Failed.\n");
return;
}
ch->printf("Ok, compression enabled.\n");
} else {
if (!ch->desc->endCompression()) {
ch->printf("Failed.\n");
return;
}
ch->printf("Ok, compression disabled.\n");
}
}
// Start compression
bool Descriptor::startCompression(void)
{
char enable[] = { IAC, SB, TELOPT_COMPRESS, WILL, SE, 0 };
if (out_compress)
return true;
z_stream *s = (z_stream *)alloc_mem(sizeof(*s));
out_compress_buf = (unsigned char *)alloc_mem(COMPRESS_BUF_SIZE);
s->next_in = NULL;
s->avail_in = 0;
s->next_out = out_compress_buf;
s->avail_out = COMPRESS_BUF_SIZE;
s->zalloc = zlib_alloc;
s->zfree = zlib_free;
s->opaque = NULL;
if (deflateInit(s, 9) != Z_OK) {
free_mem(out_compress_buf);
free_mem(s);
return false;
}
write(enable, 0);
out_compress = s;
flags.set(DESC_COMPRESS);
return true;
}
// .. and end it
bool Descriptor::endCompression(void)
{
unsigned char dummy[1];
if (!out_compress)
return true;
out_compress->avail_in = 0;
out_compress->next_in = dummy;
// No terminating signature is needed - receiver will get Z_STREAM_END
if (deflate(out_compress, Z_FINISH) != Z_STREAM_END)
return false;
if (!processCompressed()) // try to send any residual data
return false;
deflateEnd(out_compress);
free_mem(out_compress_buf);
free_mem(out_compress);
out_compress = NULL;
out_compress_buf = NULL;
flags.clear(DESC_COMPRESS);
return true;
}
Compression negotiation, and ensuring that compression output gets flushed, is done in the main i/o loop. AR's i/o loop is complex, to say the least, so this is greatly simplified.
// On connection, we send to the client:
d->printf("%c%c%c", IAC, WILL, TELOPT_EOR); // EOR for prompts
d->printf("%c%c%c", IAC, WILL, TELOPT_COMPRESS); // Offer to compress
// We handle negotiation here..
/*
* Transfer one line from input buffer to input line.
*/
void read_from_buffer (Descriptor * d)
{
int i;
// ...
/*
* Look for at least one new line.
*/
for (i = 0; d->inbuf[i] && d->inbuf[i] != '\n' && d->inbuf[i] != '\r'; i++)
{
/* Not the best way. oh well */
if (d->inbuf[i] == (signed char)IAC &&
i < MAX_INPUT_LENGTH-3 &&
(d->inbuf[i+1] == (signed char)DO ||
d->inbuf[i+1] == (signed char)DONT)) {
if (d->inbuf[i+1] == (signed char)DO) {
if (d->inbuf[i+2] == (signed char)TELOPT_EOR) {
/* wants EOR */
d->flags.set(DESC_EOR);
} else if (d->inbuf[i+2] == (signed char)TELOPT_COMPRESS) {
/* wants compression. fire it up */
d->flags.set(DESC_COMPRESS);
d->startCompression();
}
}
memmove(&d->inbuf[i], &d->inbuf[i+3], d->intop - &d->inbuf[i+3]);
d->intop -= 3;
*d->intop = 0;
i--;
continue;
}
}
// ...
}
// Within the main loop, we select() on our client connections to check for
// output.
/* I don't know how much good this will do, but heck */
if (!FD_ISSET(d->descriptor, &out_set))
sysdata.delayed_writes++;
if ((d->fcommand || d->outtop > 0 ||
(ch && ch->hasConfig(CFG_UPDATE) &&
!process_all_pulse && d->connected == CON_PLAYING &&
!d->editPtr()))
&& FD_ISSET (d->descriptor, &out_set))
{
if (!d->processOutput (TRUE))
{
char buf [MSL];
if (d->character && d->connected >= 0)
save_char_obj (d->character);
d->outtop = 0;
#if defined(unix)
sprintf (buf, "Write error: %s", strerror(last_errno));
#else
sprintf (buf, "Write error");
#endif
d->close(buf);
}
}
// Check for compressed stuff
if (FD_ISSET (d->descriptor, &out_set) &&
!d->processCompressed())
{
char buf [MSL];
if (d->character && d->connected >= 0)
save_char_obj (d->character);
d->outtop = 0;
sprintf (buf, "Write error: %s", strerror(last_errno));
d->close(buf);
}