/* $Id: mccp.c,v 1.666 2004/09/20 10:49:50 shrike Exp $ */
/************************************************************************************
* Copyright 2004 Astrum Metaphora consortium *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* *
************************************************************************************/
/************************************************************************************
* mccp.c - support functions for mccp (the Mud Client Compression Protocol) *
* *
* see http://homepages.ihug.co.nz/~icecube/compress/ and README.Rom24-mccp *
* *
* Copyright (c) 1999, Oliver Jowett <icecube@ihug.co.nz>. *
* *
* This code may be freely distributed and used if this copyright notice is *
* retained intact. *
************************************************************************************/
#include <sys/types.h>
#if !defined (WIN32)
#include <unistd.h>
#include <sys/time.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
//#include <unistd.h>
#include "merc.h"
#if !defined (WIN32)
#include <arpa/telnet.h>
#else
#include "compat/telnet.h"
#endif
void *alloc_mem(int sMem);
void free_mem(void *p, int sMem);
const char compress_start [] = { IAC, SB, TELOPT_COMPRESS, WILL, SE, '\0' } ;
const char compress2_start [] = { IAC, SB, TELOPT_COMPRESS2, IAC, SE, '\0' } ;
bool processCompressed(DESCRIPTOR_DATA *desc);
bool write_to_descriptor args( ( DESCRIPTOR_DATA *d, char *txt, int length ) );
/*
* Memory management - zlib uses these hooks to allocate and free memory
* it needs
*/
void *zlib_alloc(void *opaque, unsigned int items, unsigned int size)
{
return calloc(items, size);
}
void zlib_free(void *opaque, void *address)
{
free(address);
}
/*
* Begin compressing data on `desc'
*/
bool compressStart(DESCRIPTOR_DATA *desc, bool ver2)
{
z_stream *s;
if (desc->out_compress) /* already compressing */
return TRUE;
/* allocate and init stream, buffer */
s = (z_stream *)alloc_mem(sizeof(*s));
desc->out_compress_buf = (unsigned char *)alloc_mem(COMPRESS_BUF_SIZE);
s->next_in = NULL;
s->avail_in = 0;
s->next_out = desc->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) {
/* problems with zlib, try to clean up */
free_mem(desc->out_compress_buf, COMPRESS_BUF_SIZE);
free_mem(s, sizeof(z_stream));
return FALSE;
}
/* version1/2 support by Illinar */
if (ver2)
write_to_descriptor (desc, (char *)compress2_start,
strlen(compress2_start)) ;
else
write_to_descriptor (desc, (char *)compress_start,
strlen(compress_start)) ;
/* now we're compressing */
desc->out_compress = s;
return TRUE;
}
/* Cleanly shut down compression on `desc' */
bool compressEnd(DESCRIPTOR_DATA *desc)
{
unsigned char dummy[1];
if (!desc->out_compress)
return TRUE;
desc->out_compress->avail_in = 0;
desc->out_compress->next_in = dummy;
/* No terminating signature is needed - receiver will get Z_STREAM_END */
if (deflate(desc->out_compress, Z_FINISH) != Z_STREAM_END)
return FALSE;
if (!processCompressed(desc)) /* try to send any residual data */
return FALSE;
deflateEnd(desc->out_compress);
free_mem(desc->out_compress_buf, COMPRESS_BUF_SIZE);
free_mem(desc->out_compress, sizeof(z_stream));
desc->out_compress = NULL;
desc->out_compress_buf = NULL;
return TRUE;
}
/* Try to send any pending compressed-but-not-sent data in `desc' */
bool processCompressed(DESCRIPTOR_DATA *desc)
{
int iStart, nBlock, nWrite, len;
if (!desc->out_compress)
return TRUE;
/* Try to write out some data.. */
len = desc->out_compress->next_out - desc->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 (desc->descriptor, desc->out_compress_buf + iStart, nBlock)) < 0)
{
if (errno == EAGAIN /* ||
errno == ENOSR */)
break;
return FALSE; /* write error */
}
if (nWrite <= 0)
break;
}
if (iStart) {
/* We wrote "iStart" bytes */
if (iStart < len)
memmove(desc->out_compress_buf, desc->out_compress_buf+iStart, len - iStart);
desc->out_compress->next_out = desc->out_compress_buf + len - iStart;
}
}
return TRUE;
}
/* write_to_descriptor, the compressed case */
bool writeCompressed(DESCRIPTOR_DATA *desc, char *txt, int length)
{
z_stream *s = desc->out_compress;
s->next_in = (unsigned char *)txt;
s->avail_in = length;
while (s->avail_in) {
s->avail_out = COMPRESS_BUF_SIZE - (s->next_out - desc->out_compress_buf);
if (s->avail_out) {
int status = deflate(s, Z_SYNC_FLUSH);
if (status != Z_OK) {
/* Boom */
return FALSE;
}
}
/* Try to write out some data.. */
if (!processCompressed(desc))
return FALSE;
/* loop */
}
/* Done. */
return TRUE;
}
/* User-level compression toggle */
DO_FUN(do_compress)
{
if (!ch->desc) {
char_act("What descriptor?!", ch);
return;
}
if (!ch->desc->out_compress) {
// TODO: fix this?
// the problem is that we are forcing version1 without
// explicit WILL/DO negotiation... however most clients should
// handle this
if (!compressStart(ch->desc, FALSE)) {
char_act("Failed.", ch);
return;
}
char_act("Ok, compression enabled.", ch);
} else {
if (!compressEnd(ch->desc)) {
char_act("Failed.", ch);
return;
}
char_act("Ok, compression disabled.", ch);
}
}