--- deliantra/server/socket/lowlevel.C 2006/12/15 19:59:20 1.22 +++ deliantra/server/socket/lowlevel.C 2006/12/16 03:08:26 1.23 @@ -71,7 +71,7 @@ { LOG (llevDebug, "Connection on fd %d closed due to ack timeout (%u/%u/%u)\n", fd, (unsigned) tcpi.tcpi_last_ack_recv, (unsigned) tcpi.tcpi_last_data_sent, (unsigned) tcpi.tcpi_unacked); - status = Ns_Dead; + destroy (); } } #endif @@ -113,7 +113,7 @@ else if (res == 0) { LOG (llevError, "socket write failed, connection closed.\n"); - status = Ns_Dead; + destroy (); return; } else if (errno == EINTR) @@ -130,7 +130,7 @@ else { LOG (llevError, "socket write failed: %s\n", strerror (errno)); - status = Ns_Dead; + destroy (); return; } } @@ -138,118 +138,233 @@ socket_ev.poll (socket_ev.poll () & ~PE_W); } -/*********************************************************************** +/****************************************************************************** * - * packet functions/utilities + * Start of read routines. * - **********************************************************************/ + ******************************************************************************/ -packet &packet::operator <<(const data &v) +int +client::next_packet () { - if (room () < v.len) - reset (); - else + if (inbuf_len >= 2) { - if (v.len) + int pkt_len = (inbuf [0] << 8) | inbuf [1]; + + if (inbuf_len >= 2 + pkt_len) + return 2 + pkt_len; + + if (inbuf_len == sizeof (inbuf)) { - memcpy (cur, v.ptr, v.len); - cur += v.len; + send_packet_printf ("drawinfo %d input buffer overflow - closing connection.", NDI_RED); + destroy (); + return -1; } } - return *this; + return 0; } -packet &packet::operator <<(const data8 &v) +void +client::skip_packet (int len) { - unsigned int len = min (v.len, 0x00FF); - return *this << uint8 (len) << data (v.ptr, len); + inbuf_len -= len; + memmove (inbuf, inbuf + len, inbuf_len); } -packet &packet::operator <<(const data16 &v) +/***************************************************************************** + * Start of command dispatch area. + * The commands here are protocol commands. + ****************************************************************************/ + +// SocketCommand, PlayingCommand, should not exist with those ugly casts +#define SC(cb) (void *)static_cast(cb), +#define PC(cb) (void *)static_cast(cb), PF_PLAYER | + +/** + * Dispatch table for the server. + */ +static struct packet_type packets[] = { + {"ncom", PC(NewPlayerCmd) PF_PLAYING }, + {"command", PC(PlayerCmd) PF_PLAYING }, + + {"examine", PC(ExamineCmd) PF_PLAYING }, + {"apply", PC(ApplyCmd) PF_PLAYING }, + {"reply", PC(ReplyCmd) PF_IMMEDIATE }, + {"lookat", PC(LookAt) PF_PLAYING }, + {"lock", PC(LockItem) PF_PLAYING }, + {"mark", PC(MarkItem) PF_PLAYING }, + {"move", PC(MoveCmd) PF_PLAYING }, + {"exti", PC(ExtCmd) PF_IMMEDIATE }, /* CF+ */ + {"ext", PC(ExtCmd) 0 }, /* CF+ */ + {"mapredraw", PC(MapRedrawCmd) PF_IMMEDIATE }, /* Added: phil */ + {"mapinfo", PC(MapInfoCmd) 0 }, /* CF+ */ + + {"addme", SC(AddMeCmd) PF_IMMEDIATE }, + {"askface", SC(SendFaceCmd) PF_IMMEDIATE }, /* Added: phil */ + {"requestinfo", SC(RequestInfo) PF_IMMEDIATE }, + {"setfacemode", SC(SetFaceMode) PF_IMMEDIATE }, + {"setsound", SC(SetSound) PF_IMMEDIATE }, + {"setup", SC(SetUp) PF_IMMEDIATE }, + {"version", SC(VersionCmd) PF_IMMEDIATE }, + {"toggleextendedinfos", SC(ToggleExtendedInfos) PF_IMMEDIATE }, /*Added: tchize */ + {"toggleextendedtext", SC(ToggleExtendedText) PF_IMMEDIATE }, /*Added: tchize */ + {"asksmooth", SC(AskSmooth) PF_IMMEDIATE }, /*Added: tchize (smoothing technologies) */ +}; + +bool +client::may_execute (const packet_type *pkt) const { - unsigned int len = min (v.len, 0xFFFF); - return *this << uint16 (len) << data (v.ptr, len); + return (!(pkt->flags & PF_PLAYER) || pl) + && (!(pkt->flags & PF_PLAYING) || (pl && pl->state == ST_PLAYING)); } -packet &packet::operator <<(const char *v) +void +client::execute (const packet_type *pkt, char *data, int datalen) { - return *this << data (v, strlen (v ? v : 0)); + if (may_execute (pkt)) + { + //TODO: only one format + if (pkt->flags & PF_PLAYER) + ((void (*)(char *, int, player *))pkt->cb)((char *)data, datalen, pl); + else + ((void (*)(char *, int, client *))pkt->cb)((char *)data, datalen, this); + } + else + send_packet_printf ("drawinfo %d ERROR: you cannot execute '%s' now.", NDI_RED, pkt->name); } -void -packet::printf (const char *format, ...) +bool +client::handle_packet () { - int size = room (); + int pkt_len = next_packet (); - va_list ap; - va_start (ap, format); - int len = vsnprintf ((char *)cur, size, format, ap); - va_end (ap); + if (!pkt_len) + return false; + else if (pkt_len < 0) + { + LOG (llevError, "read error on player %s\n", + pl && pl->ob ? &pl->ob->name : "[anonymous]"); + destroy (); + return false; + } - if (len >= size) - return reset (); + inbuf [pkt_len] = 0; /* Terminate buffer - useful for string data */ - cur += len; -} + /* First, break out beginning word. There are at least + * a few commands that do not have any paremeters. If + * we get such a command, don't worry about trying + * to break it up. + */ + int datalen; + char *data = strchr ((char *)inbuf + 2, ' '); -/****************************************************************************** - * - * Start of read routines. - * - ******************************************************************************/ + if (data) + { + *data++ = 0; + datalen = pkt_len - (data - (char *)inbuf); + } + else + { + data = (char *)inbuf + 2; // better read garbage than segfault + datalen = 0; + } -int -client::read_packet () + for (packet_type *pkt = packets; pkt < packets + (sizeof (packets) / sizeof (packets[0])); ++pkt) + if (!strcmp ((char *)inbuf + 2, pkt->name)) + { + if (pkt->flags & PF_IMMEDIATE) + execute (pkt, data, datalen); + else + queue_command (pkt, data, datalen); + + goto next_packet; + } + + /* If we get here, we didn't find a valid command. Logging + * this might be questionable, because a broken client/malicious + * user could certainly send a whole bunch of invalid commands. + */ + send_packet_printf ("drawinfo %d ERROR: command '%s' not supported.", NDI_RED, (char *)inbuf + 2); +next_packet: + skip_packet (pkt_len); + + // input buffer has space again + socket_ev.poll (socket_ev.poll () | PE_R); + + return true; +} + +// callback called when socket is either readable or writable +void +client::socket_cb (iow &w, int got) { - for (;;) + //TODO remove when we have better socket cleanup logic + if (status == Ns_Dead) { - if (inbuf_len >= 2) - { - unsigned int pkt_len = (inbuf [0] << 8) | inbuf [1]; + socket_ev.poll (0); + return; + } - if (inbuf_len >= 2 + pkt_len) - return pkt_len + 2; - } + if (got & PE_W) + { + write_outputbuffer (); + + if (!outputbuffer.len) + socket_ev.poll (socket_ev.poll () & ~PE_W); + } + + if (got & PE_R) + { + //TODO: rate-limit tcp connection in better ways, important int amount = sizeof (inbuf) - inbuf_len; - if (amount <= 0) + if (!amount) { - LOG (llevError, "packet too large");//TODO - return -1; + // input buffer full + socket_ev.poll (socket_ev.poll () & ~PE_R); + return; } amount = read (fd, inbuf + inbuf_len, amount); if (!amount) { - status = Ns_Dead; - return -1; + destroy (); + return; } else if (amount < 0) { if (errno != EAGAIN && errno != EINTR) { LOG (llevError, "read error: %s\n", strerror (errno)); - return -1; + destroy (); + return; } - return 0; + // should not be here, normally } + else + { + inbuf_len += amount; - inbuf_len += amount; + cst_tot.ibytes += amount; + cst_lst.ibytes += amount; - cst_tot.ibytes += amount; - cst_lst.ibytes += amount; + cmd_ev.start (); + } } } +// called whenever we have additional commands to process void -client::skip_packet (int len) +client::cmd_cb (iw &w) { - inbuf_len -= len; - memmove (inbuf, inbuf + len, inbuf_len); + if (handle_packet () || handle_command ()) + w.start (); + else + flush (); } /******************************************************************************* @@ -272,15 +387,12 @@ int amt = 0; if (status == Ns_Dead || !buf) - { - LOG (llevDebug, "Write_To_Socket called with dead socket\n"); - return; - } + return; if ((len + outputbuffer.len) > SOCKETBUFSIZE) { - LOG (llevDebug, "Socket on fd %d has overrun internal buffer - marking as dead\n", fd); - status = Ns_Dead; + LOG (llevDebug, "socket on fd %d has overrun internal buffer - marking as dead\n", fd); + destroy (); return; } @@ -308,15 +420,6 @@ outputbuffer.len += len; } -void -client::socket_cb (iow &w, int got) -{ - write_outputbuffer (); - - if (!outputbuffer.len) - socket_ev.poll (socket_ev.poll () & ~PE_W); -} - /** * Takes a string of data, and writes it out to the socket. A very handy * shortcut function. @@ -362,6 +465,71 @@ send_packet (buf, strlen (buf)); } +void +client::send_packet_printf (const char *format, ...) +{ + packet sl; + + va_list ap; + va_start (ap, format); + sl.vprintf (format, ap); + va_end (ap); + + send_packet (sl); +} + +/*********************************************************************** + * + * packet functions/utilities + * + **********************************************************************/ + +packet &packet::operator <<(const data &v) +{ + if (room () < v.len) + reset (); + else + { + if (v.len) + { + memcpy (cur, v.ptr, v.len); + cur += v.len; + } + } + + return *this; +} + +packet &packet::operator <<(const data8 &v) +{ + unsigned int len = min (v.len, 0x00FF); + return *this << uint8 (len) << data (v.ptr, len); +} + +packet &packet::operator <<(const data16 &v) +{ + unsigned int len = min (v.len, 0xFFFF); + return *this << uint16 (len) << data (v.ptr, len); +} + +packet &packet::operator <<(const char *v) +{ + return *this << data (v, strlen (v ? v : 0)); +} + +void +packet::vprintf (const char *format, va_list ap) +{ + int size = room (); + + int len = vsnprintf ((char *)cur, size, format, ap); + + if (len >= size) + return reset (); + + cur += len; +} + /****************************************************************************** * * statistics logging functions.