# mccp - sample code

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.

## Descriptor modifications

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;

    }

## Main loop modifications

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);

        }

[Mail me] [Up]