/
rogue25b1/
rogue25b1/space/planets/
rogue25b1/space/prototypes/
rogue25b1/space/ships/
/***************************************************************************\
[*]    ___    ____   ____   __   __  ____ [*]   ROGUE: ROM With Attitude  [*]
[*]   /#/ )  /#/  ) /#/  ) /#/  /#/ /#/   [*]    All rights reserved      [*]
[*]  /#/ <  /#/  / /#/ _  /#/  /#/ /#/--  [*]   Copyright(C) 2000-2001    [*]
[*] /#/   \(#(__/ (#(__/ (#(__/#/ (#(___  [*] Kenneth Conley (Mendanbar)  [*]
[*]  Expression of Digital Creativity..   [*]  scmud@mad.scientist.com    [*]
[-]---------------------------------------+-+-----------------------------[-]
[*] File: mudproto.cpp                                                    [*]
[*] Usage: MUD Client Protocols (MSP, MXP, MCCP)                          [*]
\***************************************************************************/

/*
 * Uh.. something something..
 * - Mendanbar
 */

#include "merc.h"
#include "telnet.h"
#include "clientopt.h"

bool processCompress		args( (DESCRIPTOR_DATA *desc) );
bool write_to_descriptor	args( (DESCRIPTOR_DATA *d, char *txt, int length) );

static	char	mcp_start[] = 	{ IAC, SB, TELOPT_MCP, IAC, SE, '\0' };
static	char	mxp_start[] =	{ IAC, SB, TELOPT_MXP, IAC, SE, '\0' };

void *zlib_alloc(void *opaque, UInt32 items, UInt32 size) {
	return calloc(items, size);
}

void zlib_free(void *opaque, void *address) {
	free(address);
}

bool compressStart(DESCRIPTOR_DATA *desc) {
    z_stream *s;

    if (desc->out_compress)
	return TRUE;

    s = (z_stream *)alloc_mem(sizeof(*s));
    desc->out_compress_buf = (unsigned char *)alloc_mem(COMPRESS_BUF_SIZE);

    if (!s) {
	mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: compressStart: Couldn't find a socket to compress.");
	close_socket(desc);
	return FALSE;
    }

    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) {
	mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: problems with zlib, trying to clean up.");
	free_mem(desc->out_compress_buf, COMPRESS_BUF_SIZE);
	free_mem(s, sizeof(z_stream));
	return FALSE;
    }

    SET_BIT(DESC_FLAGS(desc), DESC_MCPON);
    write_to_descriptor(desc, mcp_start, strlen(mcp_start));
    desc->out_compress = s;
    return TRUE;
}

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;

    if (deflate(desc->out_compress, Z_FINISH) != Z_STREAM_END)
	return FALSE;

    if (!processCompressed(desc))
	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;
    REMOVE_BIT(DESC_FLAGS(desc), DESC_MCPON);
    return TRUE;
}

bool processCompressed(DESCRIPTOR_DATA *desc) {
    int iStart, nBlock, nWrite, len;

    if (!desc->out_compress)
	return TRUE;

    len = desc->out_compress->next_out - desc->out_compress_buf;

    if (len > 0) {
	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;
	    }
	    if (nWrite <= 0)
		break;
	}

	if (iStart) {
	    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;
}

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)
		return FALSE;	// Boom?
	}

	if (!processCompressed(desc))
	    return FALSE;
    }
    return TRUE;
}

void toggle_mxp(DESCRIPTOR_DATA *d) {
    if (!DESC_FLAGGED(d, DESC_MXPON)) {
	// Initialize and setup elements...
	SET_BIT(DESC_FLAGS(d), DESC_MXPON);
	write_to_buffer(d, mxp_start, strlen(mxp_start));
	write_to_buffer(d, MXPMODE(MXP_PERM_SECURE), 0);

	write_to_buffer(d, MXP_TAG("!-- Setup MXP elements --"), 0);
	write_to_buffer(d, MXP_TAG("!ELEMENT Ex '<send>' FLAG=RoomExit"), 0);
	write_to_buffer(d, MXP_TAG("!ELEMENT Exit '<send exits>'"), 0);
    } else {
	// turn it off
	REMOVE_BIT(DESC_FLAGS(d), DESC_MXPON);
	write_to_buffer(d, MXPMODE(MXP_PERM_LOCKED), 0);
    }
    return;
}

void toggle_compression(DESCRIPTOR_DATA *d) {
    if (d->out_compress)
	compressEnd(d);
    else
	compressStart(d);
    return;
}

ACMD(do_compress) {
    if (!ch->desc) {
	ch->Send("What descriptor?\n\r");
	return;
    }

    if (!ch->desc->out_compress) {
	if (!compressStart(ch->desc)) {
	    ch->Send("Failed.\n\r");
	    return;
	}
	ch->Send("Okay, compression enabled.\n\r");
    } else {
	if (!compressEnd(ch->desc)) {
	    ch->Send("Failed.\n\r");
	    return;
	}
	ch->Send("Okay, compression disabled.\n\r");
    }
    return;
}

void will_telopt(DESCRIPTOR_DATA *d, unsigned char opt) {
    unsigned char will_option[] = { IAC, WILL, opt, '\0' };
    write_to_buffer(d, (char *)will_option, 0);
}

void negotiate_telopt(DESCRIPTOR_DATA *d) {
    // Extention Protocal
    will_telopt(d, TELOPT_MXP);

    // Sound Protocal
    will_telopt(d, TELOPT_MSP);

    if (!d->out_compress)
	will_telopt(d, TELOPT_MCP);
}

int process_telopt(DESCRIPTOR_DATA *d, int first_iac) {
    bool incomplete = FALSE;
    int l, iac_sb_index, i = first_iac;
    unsigned char *in = (unsigned char *)d->inbuf;
    bool mxp_start = FALSE, mxp_stop = FALSE, mccp_stop = FALSE;

    for (l = 0; in[i] == IAC && !incomplete && l < 20; l++) {
	switch (in[i+1]) {
	    case IAC:
	    case '\0':
		incomplete = TRUE;
		break;
	    case DO:
		switch (in[i+2]) {
		    case '\0':
			i -= 3;
			incomplete = TRUE;
			break;
		    case TELOPT_MCP:
			SET_BIT(DESC_FLAGS(d), DESC_CANZLIB);
			toggle_compression(d);
			break;
		    case TELOPT_MSP:
			SET_BIT(DESC_FLAGS(d), DESC_HASMSP | DESC_MSPON);
			break;
		    case TELOPT_MXP:
			SET_BIT(DESC_FLAGS(d), DESC_HASMXP);
			toggle_mxp(d);
			break;
		    default:
//			mudlogf(BRF, LVL_STAFF, TRUE, "process_telopt: unknown DO code (%s)", in[i+2]);
			break;
		}
		i += 3;
		break;
	    case DONT:
		switch (in[i+2]) {
		    case '\0':
			i -= 3;
			incomplete = true;
			break;
		    case TELOPT_MCP:
		    case TELOPT_MSP:
		    case TELOPT_MXP:
//			do nothing
			break;
		    default:
			break;
		}
		i += 3;
		break;
	    case SB:
		iac_sb_index = i;
		i += 2;
		while (in[i] && in[i] != SE)
		    i++;
		if (in[i] == SE) {
		    i++;
//		    subopt_check(d, iac_sb_index);
		} else {
		    i = iac_sb_index;
		    incomplete = TRUE;
		}
		break;
	    default:
		if (in[+2] == '\0')
		    incomplete = TRUE;
		else {
		    mudlogf(BRF, LVL_STAFF, TRUE, "process_telopt: ignoring IAC %d %d", in[i+1], in[i+2]);
		    i += 3;
		}
		break;
	}
    }

    memmove(&d->inbuf[first_iac], &d->inbuf[i], strlen(&d->inbuf[i])+1);

    if (l == 20)
	mudlogf(BRF, LVL_STAFF, TRUE, "process_telopt: More than 20 telnet options");

    return 0;
}

void set_desc_flags(DESCRIPTOR_DATA *d) {
    CHAR_DATA *ch = Original(d);

    if (!ch)
	return;

    REMOVE_BIT(PLR_FLAGS(ch), PLR_PUEBLO | PLR_MSP );

    if (DESC_FLAGGED(d, DESC_PUEBLO))	SET_BIT(PLR_FLAGS(ch), PLR_PUEBLO);
}

int count_mxp_tags(int mxp, const char *txt, int length) {
    char c;
    const char *p;
    int count = 0, InTag = 0, InEntity = 0;

    for (p = txt, count = 0; length > 0; p++, length--) {
	c = *p;
	if (InTag) {
	    if (!mxp)
		count--;
	    if (c == MXP_ENDc)
		InTag = FALSE;
	} else if (InEntity) {
	    if (!mxp)
		count--;
	    if (c == ';')
		InEntity = FALSE;
	} else {
	    switch (c) {
		case MXP_BEGc:
		    InTag = TRUE;
		     if (!mxp)
			count--;
		    break;

		case MXP_ENDc:
		    if (!mxp)
			count--;
		    break;

		case MXP_AMPc:
		    InEntity = TRUE;
		    if (!mxp)
			count--;
		    break;

		default:
		    if (mxp) {
			switch (c) {
			    case '<':
			    case '>':
				count += 3;
				break;
			    case '&':
				count += 4;
				break;
			    case '"':
				count += 5;
				break;
			}
		    }
		break;
	    }
	}
    }
    return count;
}

void convert_mxp_tags(int mxp, char *dest, const char *src, int len) {
    char *pd, c;
    const char *ps;
    bool InTag = FALSE, InEntity = FALSE;

    for (ps = src, pd = dest; len > 0; ps++, len--) {
	c = *ps;
	if (InTag) {
	    if (c == MXP_ENDc) {
		InTag = FALSE;
		if (mxp)
		    *pd++ = '>';
	    } else if (mxp)
		*pd++ = c;
	} else if (InEntity) {
	    if (mxp)
		*pd++ = c;
	    if (c == ';')
		InEntity = FALSE;
	} else {
	    switch (c) {
		case MXP_BEGc:
		    InTag = TRUE;
		    if (mxp)
			*pd++ = '<';
		    break;
		case MXP_ENDc:
		    if (mxp)
			*pd++ = '>';
		    break;
		case MXP_AMPc:
		    InEntity = TRUE;
		    if (mxp)
			*pd++ = '&';
		    break;
		default:
		    if (mxp) {
			switch (c) {
			    case '<':
				memcpy(pd, "&lt;", 4);
				pd += 4;
				break;
			    case '>':
				memcpy(pd, "&gt;", 4);
				pd += 4;
				break;
			    case '&':
				memcpy(pd, "&amp;", 5);
				pd += 5;
				break;
			    case '"':
				memcpy(pd, "&quot;", 6);
				pd += 6;
				break;
			    default:
				*pd++ = c;
				break;
			}
		    } else
			*pd++ = c;
		break;
	    }
	}
    }
}