package com.planet_ink.coffee_mud.core.intermud.packets;
import com.planet_ink.coffee_mud.core.intermud.imc2.*;
import com.planet_ink.coffee_mud.core.intermud.packets.*;
import com.planet_ink.coffee_mud.core.intermud.persist.*;
import com.planet_ink.coffee_mud.core.intermud.server.*;
import com.planet_ink.coffee_mud.core.intermud.net.*;
import com.planet_ink.coffee_mud.core.intermud.*;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;
/**
* com.planet_ink.coffee_mud.core.intermud.packets.Intermud
* Copyright (c) 1996 George Reese
* This source code may not be modified, copied,
* redistributed, or used in any fashion without the
* express written consent of George Reese.
*
* This is the TCP/IP interface to version 3 of the
* Intermud network.
*/
import java.io.*;
import java.net.Socket;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* The Intermud class is the central focus of incoming
* and outgoing Intermud 3 packets. It creates the link
* to the I3 router, handles reconnection, and routing
* of packets to the mudlib. The mudlib is responsible
* for providing two specific objects to interface with
* this object:
* an implementation of com.planet_ink.coffee_mud.core.intermud.packets.ImudServices
* an implementation of com.planet_ink.coffee_mud.core.intermud.persist.PersistentPeer
* To start up the Intermud connection, call the class
* method setup().
* The class itself creates an instance of itself and
* serves as a way to interface to the rest of the mudlib.
* When the mudlib needs to send a packet, it sends it
* through a class method which then routes it to the
* proper instance of Intermud.
* @author George Reese
* @version 1.0
* @see com.planet_ink.coffee_mud.core.intermud.packets.ImudServices
* @see com.planet_ink.coffee_mud.core.intermud.persist.PersistentPeer
*/
public class Intermud implements Runnable, Persistent, Serializable
{
public static final long serialVersionUID=0;
static private Intermud thread = null;
/**
* Sends a packet to the router. The packet must
* be a valid subclass of com.planet_ink.coffee_mud.core.intermud.packets.Packet.
* This method will then route the packet to the
* currently running Intermud instance.
* @param p an instance of a subclass of com.planet_ink.coffee_mud.core.intermud.packets.Packet
* @see com.planet_ink.coffee_mud.core.intermud.packets.Packet
*/
static public void sendPacket(Packet p) {
if(!isConnected()) return;
thread.send(p);
}
/**
* Creates the initial link to an I3 router.
* It will handle subsequent reconnections as needed
* for as long as the mud process is running.
* @param imud an instance of the mudlib implementation of com.planet_ink.coffee_mud.core.intermud.packets.ImudServices
* @param peer and instance of the mudlib implementation of com.planet_ink.coffee_mud.core.intermud.packets.IntermudPeer
* @see com.planet_ink.coffee_mud.core.intermud.packets.ImudServices
* @see com.planet_ink.coffee_mud.core.intermud.persist.PersistentPeer
*/
static public void setup(ImudServices imud, PersistentPeer peer) {
if( thread != null ) {
return;
}
thread = new Intermud(imud, peer);
}
/**
* Translates a user entered mud name into the mud's
* canonical name.
* @param mud the user entered mud name
* @return the specified mud's canonical name
*/
static public String translateName(String mud) {
if(!isConnected()) return "";
return thread.getMudNameFor(mud);
}
/**
* Returns a String representing the local channel
* name for the specified remote channel by
* calling the ImudServices implementation of
* getLocalChannel().
* @param c the remote channel name
* @return the local channel name for the specified remote channel name
* @see com.planet_ink.coffee_mud.core.intermud.packets.ImudServices#getLocalChannel
*/
static public String getLocalChannel(String c ) {
if(!isConnected()) return "";
return thread.intermud.getLocalChannel(c);
}
/**
* Returns a String representing the remote channel
* name for the specified local channel by
* calling the ImudServices implementation of
* getRemoteChannel().
* @param c the local channel name
* @return the remote channel name for the specified local channel name
* @see com.planet_ink.coffee_mud.core.intermud.packets.ImudServices#getRemoteChannel
*/
static public String getRemoteChannel(String c) {
if(!isConnected()) return "";
return thread.intermud.getRemoteChannel(c);
}
/**
* Determines whether or not the specified mud is up.
* You may pass user entered mud names, as this method
* will take the time to convert to a canonical name.
* @param mud the name of the mud being checked
* @return true if the mud is currently up, false otherwise
*/
static public boolean isUp(String mud) {
if(!isConnected()) return false;
Mud m = thread.getMud(mud);
if( m == null )
return false;
return (m.state == -1);
}
private boolean connected;
private Socket connection;
private Thread input_thread;
private ImudServices intermud;
private int modified;
private DataOutputStream output;
private PersistentPeer peer;
private SaveThread save_thread;
public boolean shutdown=false;
public DataInputStream input;
public int attempts;
public Hashtable banned;
public ChannelList channels;
public MudList muds;
public Vector name_servers;
public int password;
private Intermud(ImudServices imud, PersistentPeer p)
{
super();
intermud = imud;
peer = p;
peer.setPersistent(this);
connected = false;
password = -1;
attempts = 0;
input_thread = null;
channels = new ChannelList(-1);
muds = new MudList(-1);
banned = new Hashtable();
name_servers = new Vector();
String s=CMProps.getVar(CMProps.SYSTEM_I3ROUTERS);
Vector V=CMParms.parseCommas(s,true);
for(int v=0;v<V.size();v++)
{
s=(String)V.elementAt(v);
Vector V2=CMParms.parseAny(s,":",true);
if(V2.size()>=3)
name_servers.addElement(new NameServer((String)V2.firstElement(),CMath.s_int((String)V2.elementAt(1)), (String)V2.elementAt(2)));
}
modified = Persistent.UNMODIFIED;
try {
restore();
}
catch( PersistenceException e ) {
password = -1;
channels = new ChannelList(-1);
muds = new MudList(-1);
Log.errOut("Intermud",e);
}
save_thread = new SaveThread(this);
save_thread.setDaemon(true);
save_thread.start();
connect();
}
// Handles an incoming channel list packet
private synchronized void channelList(Vector packet) {
Hashtable list = (Hashtable)packet.elementAt(7);
Enumeration keys = list.keys();
synchronized( channels ) {
channels.setChannelListId(((Integer)packet.elementAt(6)).intValue());
while( keys.hasMoreElements() ) {
Channel c = new Channel();
Object ob;
c.channel = (String)keys.nextElement();
ob = list.get(c.channel);
if( ob instanceof Integer ) {
removeChannel(c);
}
else {
Vector info = (Vector)ob;
c.owner = (String)info.elementAt(0);
c.type = ((Integer)info.elementAt(1)).intValue();
addChannel(c);
}
}
}
modified = Persistent.MODIFIED;
}
public static NameServer getNameServer()
{
if(thread==null) return null;
if(thread.name_servers==null) return null;
if(thread.name_servers.size()==0) return null;
return (NameServer)thread.name_servers.elementAt(0);
}
private synchronized void connect() {
if(shutdown) return;
attempts++;
try {
for(int i=0;i<name_servers.size();i++)
{
NameServer n = (NameServer)name_servers.elementAt(i);
connection = new Socket(n.ip, n.port);
output = new DataOutputStream(connection.getOutputStream());
send("({\"startup-req-3\",5,\"" + intermud.getMudName() + "\",0,\"" +
n.name + "\",0," + password +
"," + muds.getMudListId() + "," + channels.getChannelListId() + "," + intermud.getMudPort() +
",0,0,\""+intermud.getMudVersion()+"\",\""+intermud.getMudVersion()+"\",\""+intermud.getMudVersion()+"\",\"CoffeeMud\"," +
"\""+intermud.getMudState()+"\",\""+CMProps.getVar(CMProps.SYSTEM_I3EMAIL).toLowerCase()+"\",([" +
"\"who\":1,\"finger\":1,\"channel\":1,\"tell\":1,\"locate\":1,]),([]),})");
connected = true;
input_thread = new Thread(this);
input_thread.setDaemon(true);
input_thread.setName("Intermud");
input_thread.start();
Enumeration e = intermud.getChannels();
while( e.hasMoreElements() ) {
String chan = (String)e.nextElement();
send("({\"channel-listen\",5,\"" + intermud.getMudName() + "\",0,\"" +
n.name + "\",0,\"" + chan + "\",1,})");
}
Log.sysOut("I3 router connection to "+n.ip+": "+n.port);
break;
}
}
catch( Exception e ) {
try { Thread.sleep(attempts * 100); }
catch( InterruptedException ignore )
{
if(shutdown)
{
Log.sysOut("Intermud","Shutdown!");
return;
}
}
connect();
}
}
// Handles an incoming error packet
private synchronized void error(Vector packet) {
Object target = packet.elementAt(5);
String msg = (String)packet.elementAt(7);
if( target instanceof Integer ) {
I3Exception e;
e = new I3Exception(msg);
if(e.getMessage()!=null)
Log.errOut("InterMud","276-"+e.getMessage());
}
else {
}
}
private synchronized void mudlist(Vector packet) {
Hashtable list;
Enumeration keys;
synchronized( muds ) {
muds.setMudListId(((Integer)packet.elementAt(6)).intValue());
list = (Hashtable)packet.elementAt(7);
keys = list.keys();
while( keys.hasMoreElements() ) {
Mud mud = new Mud();
Object info;
mud.mud_name = (String)keys.nextElement();
info = list.get(mud.mud_name);
if( info instanceof Integer ) {
removeMud(mud);
}
else {
Vector v = (Vector)info;
int total=0;
for(int vi=0;vi<v.size();vi++)
if(v.elementAt(vi) instanceof String)
total+=((String)v.elementAt(vi)).length();
if(total<1024)
{
mud.state = ((Integer)v.elementAt(0)).intValue();
mud.address = (String)v.elementAt(1);
mud.player_port = ((Integer)v.elementAt(2)).intValue();
mud.tcp_port = ((Integer)v.elementAt(3)).intValue();
mud.udp_port = ((Integer)v.elementAt(4)).intValue();
mud.mudlib = (String)v.elementAt(5);
mud.base_mudlib = (String)v.elementAt(6);
mud.driver = (String)v.elementAt(7);
mud.mud_type = (String)v.elementAt(8);
mud.status = (String)v.elementAt(9);
mud.admin_email = (String)v.elementAt(10);
addMud(mud);
}
}
}
}
}
// Hashtable services = (Hashtable)v.elementAt(11);
// Hashtable other_info = (Hashtable)v.elementAt(12);
public void restore() throws PersistenceException {
if( modified != Persistent.UNMODIFIED ) {
throw new PersistenceException("Restoring over changed data.");
}
peer.restore();
modified = Persistent.UNMODIFIED;
}
public static boolean isConnected()
{
if(thread==null) return false;
return thread.connected;
}
public void run() {
try {
connection.setSoTimeout(60000);
input = new DataInputStream(connection.getInputStream());
}
catch( java.io.IOException e ) {
input = null;
connected = false;
}
while( connected && (!shutdown)) {
Vector data;
try { Thread.sleep(100); }
catch( InterruptedException e )
{
if(shutdown)
{
Log.sysOut("Intermud","Shutdown!!");
return;
}
}
// Read a packet from the router
{
String str;
try {
int len=0;
while(!shutdown){try{
len = input.readInt();
break;
}catch(java.io.IOException e){if((e.getMessage()==null)||(e.getMessage().toUpperCase().indexOf("TIMED OUT")<0)) throw e;}}
if(len>65536)
{
int skipped=0;
while(skipped<len)
skipped+=input.skipBytes(len);
Log.errOut("Got illegal packet: "+skipped+"/"+len+" bytes.");
continue;
}
byte[] tmp = new byte[len];
while(!shutdown){try{
input.readFully(tmp);
break;
}catch(java.io.IOException e){if((e.getMessage()==null)||(e.getMessage().toUpperCase().indexOf("TIMED OUT")<0)) throw e;}}
str=new String(tmp);
}
catch( java.io.IOException e ) {
data = null;
str = null;
connected = false;
try { Thread.sleep(1200); }
catch (InterruptedException ee)
{
if(shutdown)
{
Log.sysOut("Intermud","Shutdown!!!");
return;
}
}
connect();
String errMsg=e.getMessage()==null?e.toString():e.getMessage();
if(errMsg!=null) Log.errOut("InterMud","384-"+errMsg);
return;
}
try {
if(CMSecurity.isDebugging("I3"))
Log.sysOut("Intermud","Receiving: "+str);
data = (Vector)LPCData.getLPCData(str);
}
catch( I3Exception e ) {
String errMsg=e.getMessage()==null?e.toString():e.getMessage();
if(errMsg!=null) Log.errOut("InterMud","389-"+errMsg);
continue;
}
}
// Figure out the packet type and send it to the mudlib
{
String type = (String)data.elementAt(0);
if( type.equals("channel-m") || type.equals("channel-e") || type.equals("channel-t") ) {
try {
ChannelPacket p = new ChannelPacket(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","0-"+e.getMessage());
}
}
else if( type.equals("chan-who-req") ) {
try {
ChannelWhoRequest p = new ChannelWhoRequest(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","1-"+e.getMessage());
}
}
else if( type.equals("channel-add") ) {
try {
ChannelAdd p = new ChannelAdd(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","2-"+e.getMessage());
}
}
else if( type.equals("channel-remove") ) {
try {
ChannelDelete p = new ChannelDelete(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","3-"+e.getMessage());
}
}
else if( type.equals("channel-listen") ) {
try {
ChannelListen p = new ChannelListen(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","4-"+e.getMessage());
}
}
else if( type.equals("chan-who-reply") ) {
try {
ChannelWhoReply p = new ChannelWhoReply(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","5-"+e.getMessage());
}
}
else if( type.equals("chanlist-reply") ) {
channelList(data);
}
else if( type.equals("locate-reply") ) {
try {
LocateReplyPacket p = new LocateReplyPacket(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","6-"+e.getMessage());
}
}
else if( type.equals("locate-req") ) {
try {
LocateQueryPacket p = new LocateQueryPacket(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","7-"+e.getMessage());
}
}
else if( type.equals("mudlist") ) {
mudlist(data);
}
else if( type.equals("startup-reply") ) {
startupReply(data);
}
else if( type.equals("tell") ) {
try {
TellPacket p = new TellPacket(data);
intermud.receive(p);
}
catch( InvalidPacketException e ) {
Log.errOut("Intermud","8-"+e.getMessage());
}
}
else if( type.equals("who-req") ) {
WhoPacket p = new WhoPacket(data);
intermud.receive(p);
}
else if( type.equals("who-reply") ) {
WhoPacket p = new WhoPacket(data);
intermud.receive(p);
}
else if( type.equals("error") ) {
error(data);
}
else if( type.equals("ucache-update") ) {
// i have NO idea what to do here
}
else {
Log.errOut("Intermud","Other packet: " + type);
}
}
}
}
public void save() throws PersistenceException {
if( modified == Persistent.UNMODIFIED ) {
return;
}
peer.save();
modified = Persistent.UNMODIFIED;
}
/**
* Sends any valid subclass of Packet to the router.
* @param p the packet to send
*/
public void send(Packet p) {
send(p.toString());
}
// Send a formatted mud mode packet to the router
private void send(String str)
{
if(CMSecurity.isDebugging("I3"))
Log.sysOut("Intermud","Sending: "+str);
try
{
// Remove non-printables, as required by the I3 specification
// (Contributed by David Green <green@couchpotato.net>)
byte[] packet = str.getBytes("ISO-8859-1");
for (int i = 0; i < packet.length; i++)
{
// 160 is a non-breaking space. We'll consider that "printable".
if (packet[i] < 32 || (packet[i] >= 127 && packet[i] <= 159))
{
// Java uses it as a replacement character,
// so it's probably ok for us too.
packet[i] = '?';
}
}
output.writeInt(packet.length);
output.write(packet);
}
catch( java.io.IOException e ) {
String errMsg=e.getMessage()==null?e.toString():e.getMessage();
if(errMsg!=null) Log.errOut("InterMud","557-"+errMsg);
}
}
// Handle a startup reply packet
private synchronized void startupReply(Vector packet) {
Vector router_list = (Vector)packet.elementAt(6);
if( router_list != null ) {
Vector router = (Vector)router_list.elementAt(0);
NameServer name_server = (NameServer)name_servers.elementAt(0);
if( !name_server.name.equals(router.elementAt(0)) ) {
// create new name server and connect
return;
}
}
password = ((Integer)packet.elementAt(7)).intValue();
modified = Persistent.MODIFIED;
}
/**
* Shuts down the connection to the router without
* reconnecting.
* @see java.lang.Runnable#run()
*/
public void stop()
{
connected = false;
shutdown=true;
try { if(input!=null) input.close();}catch(Exception e){}
try { if(connection!=null) connection.close();}catch(Exception e){}
if(save_thread!=null)
{
try { save_thread.close();}catch(Exception e){}
try { CMLib.killThread(save_thread,100,1);}catch(Exception e){}
}
save_thread=null;
try { save(); }
catch( PersistenceException e ) { }
try { if(input_thread!=null) CMLib.killThread(input_thread,100,1); }catch(Exception e){}
input_thread=null;
shutdown=false;
}
/**
* Adds a channel to the channel list.
* This does not subscribe the mud to that channel.
* In order to subscribe, the channel needs to be
* added to the ImudServices implementation's getChannels()
* method.
* @param c the channel to add to the list of known channels
* @see com.planet_ink.coffee_mud.core.intermud.packets.ImudServices#getChannels
*/
public void addChannel(Channel c) {
channels.addChannel(c);
}
/**
* Removes a channel from the channel list.
* @param c the channel to remove
*/
public void removeChannel(Channel c) {
channels.removeChannel(c);
}
/**
* @return the list of currently known channels
*/
public ChannelList getChannelList() {
return channels;
}
/**
* Sets the channel list to a new channel list.
* @param list the new channel list
*/
public void setChannelList(ChannelList list) {
channels = list;
}
/**
* Adds a mud to the list of known muds.
* @param m the mud to add
*/
public void addMud(Mud m) {
muds.addMud(m);
modified = Persistent.MODIFIED;
}
private Mud getMud(String mud_name) {
return muds.getMud(getMudNameFor(mud_name));
}
/**
* Removed a mud from the list of known muds.
* @param m the mud to remove
*/
public void removeMud(Mud m) {
muds.removeMud(m);
modified = Persistent.MODIFIED;
}
/**
* @return the list of known muds
*/
public MudList getMudList() {
return muds;
}
/**
* @return the list of known muds
*/
public static MudList getAllMudsList() {
if(!isConnected()) return new MudList(-1);
return thread.muds;
}
/**
* @return the list of known muds
*/
public static ChannelList getAllChannelList() {
if(!isConnected()) return new ChannelList();
return thread.channels;
}
/**
* Sets the list of known muds to the specified list.
* @param list the new list of muds
*/
public void setMudList(MudList list) {
muds = list;
}
private String getMudNameFor(String mud)
{
Enumeration list = muds.getMuds().keys();
mud = mud.toLowerCase().replace('.', ' ');
while( list.hasMoreElements() ) {
String str = (String)list.nextElement();
if( mud.equalsIgnoreCase(str) ) {
return str;
}
}
list = muds.getMuds().keys();
while( list.hasMoreElements() ) {
String str = (String)list.nextElement();
if( CMLib.english().containsString(str,mud) ) {
return str;
}
}
return mud;
}
/**
* @return the I3 password for this mud
*/
public int getPassword() {
return password;
}
/**
* Sets the Intermud 3 password.
* @param pass the new password
*/
public void setPassword(int pass) {
password = pass;
}
public static void shutdown()
{
if(thread!=null)
thread.stop();
thread=null;
}
}
class SaveThread extends Thread {
private Intermud intermud;
protected boolean closed=false;
public SaveThread(Intermud imud) {
super("Intermud save");
setName("Intermud save");
setDaemon(true);
intermud = imud;
}
public void close()
{ closed=true; }
public void run() {
while( !closed ) {
try {
Thread.sleep(120000);
if(!closed) intermud.save();
}
catch (InterruptedException e)
{
Log.sysOut("Intermud","Save Thread Shutdown!");
return;
}
catch( PersistenceException e ) {
}
}
}
}